Path tracing

Hi!
I wanted to share this (biased) implementation of path tracing. It’s slow, so you might need a strong device!

function setup()
    sin,cos,rad=math.sin,math.cos,math.rad
    spriteMode(CORNER)
    parameter.watch("1/DeltaTime")
    parameter.number("Moving",0,0.1,0)
    RES=4
    Screen=image(WIDTH/RES,HEIGHT/RES)
    M=mesh()
    M.vertices={vec2(0,0),vec2(Screen.width,0),vec2(Screen.width,Screen.height),
    vec2(Screen.width,Screen.height),vec2(0,Screen.height),vec2(0,0)}
    M.texCoords={vec2(0,0),vec2(1,0),vec2(1,1),vec2(1,1),vec2(0,1),vec2(0,0)}
    M.shader=shader(PTverts,PTfrag)
    M.shader.SColors={vec3(1,1,1),vec3(0.6,0.6,1),vec3(0.8,0.8,0.8),vec3(1,1,1),vec3(1,1,1),vec3(1,0.3,0.3),vec3(0.3,0.3,1)}
    M.shader.Spheres={vec4(0,0.3,0,0.3),vec4(0.8,0.3,-1,0.3),
    vec4(0,-100,0,100),vec4(0,104,0,100),vec4(0,0,-102,100),vec4(-102,0,0,100),vec4(102,0,0,100)}
    M.shader.Lights={vec4(-0.3,1,0,0.15),vec4(0,1,6,0.15)}
    M.shader.LColors={vec3(1,1,1),vec3(0,1,1)}
    --Variabler
    Angle=vec2(180,0)
    Eye=vec3(0,0,1)
    Pos=vec3(0,1.6,3)
end

function draw()
    Eye=vec3(sin(rad(Angle.x)),Angle.y,cos(rad(Angle.x))):normalize()
    _TRay=Eye:cross(vec3(0,1,0))
    Pos=Pos+Eye*Moving
    --Draw
    noSmooth()
    setContext(Screen)
    M.shader.Ray=Eye
    M.shader.TRay=_TRay:normalize()
    M.shader.BRay=_TRay:cross(Eye):normalize()
    M.shader.Position=Pos
    M.shader.Time=ElapsedTime
    M:draw()
    setContext()
    background(0, 0, 0, 255)
    sprite(Screen,0,0,WIDTH,HEIGHT)
end

function touched(t)
    if t.state==BEGAN then
        fpos=vec2(t.x,t.y)
        fangle=vec2(Angle.x,Angle.y)
    end
    if t.state==MOVING then
        Angle.x=fangle.x-(t.x-fpos.x)/2
        Angle.y=(t.y-fpos.y)/80
    end
    if t.state==ENDED then
        fpos=vec2(t.prevX,t.prevY)
        fangle=vec2(Angle.x,Angle.y)
    end
end

PTverts=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
uniform vec3 Ray;
uniform vec3 TRay;
uniform vec3 BRay;
varying mat3 Eye;
varying highp vec2 vt;
#define Aspect 749./768.
void main() {
    vt=texCoord;
    Eye=mat3(TRay.x,BRay.x,Ray.x,TRay.y,BRay.y,Ray.y,TRay.z,BRay.z,Ray.z);
    gl_Position=modelViewProjection*position;
}
]]

PTfrag=[[
precision highp float;
varying highp vec2 vt;
varying mat3 Eye;
uniform vec3 Position;
#define NSpheres 7
#define NLights 2
uniform vec4 Spheres[NSpheres];
uniform vec3 SColors[NSpheres];
uniform vec4 Lights[NLights];
uniform vec3 LColors[NLights];
uniform float Time;
//Quali
#define Far 200.
#define GLSamples 8

//Const
#define IGLS 1./max(float(GLSamples),1.)
#define PI 3.14159
#define Aspect 749./768.
//#define Aspect 1024./768.

vec2 seed=vt*vec2(Time*34.69467);
vec2 noise() {
    seed+=vec2(-1.,1.);
    return vec2(fract(sin(dot(seed.xy,vec2(12.9898,78.233)))*43758.5453),
    fract(cos(dot(seed,vec2(4.898,7.23)))*23421.631));
}

float sphere(vec3 pos, vec3 dir, vec4 sph) {
    vec3 co=pos-sph.xyz;
    float b=dot(co,dir);
    float c=dot(co,co)-sph.w*sph.w;
    float h=b*b-c;
    //if (h<0.) return -1.;
    return -b-sqrt(h);
}

int Trace(vec3 pos, vec3 ray, out vec3 col, out vec3 cpos, out vec3 norm) {
    float d=Far; float dd; int result=0;
    for (int i=0; i<NSpheres; i++) {
        dd=sphere(pos,ray,Spheres[i]);
        if (dd<0. || dd>d) continue;
        result=1;
        d=dd; cpos=pos+ray*dd;
        col=SColors[i]; norm=normalize(cpos-Spheres[i].xyz);
    }
    for (int i=0; i<NLights; i++) {
        dd=sphere(pos,ray,Lights[i]);
        if (dd<0. || dd>d) continue;
        d=dd;
        col=LColors[i];
        return 2;
    }
    return result;
}

mat3 TBN(vec3 N) {
    vec3 Nt,Nb;
    if (abs(N.x)>abs(N.y)) Nt=vec3(N.z,0.,-N.x)/sqrt(N.x*N.x+N.z*N.z);
    else Nt=vec3(0.,-N.z,N.y)/sqrt(N.y*N.y+N.z*N.z);
    Nb=normalize(cross(N,Nt));
    return mat3(Nt.x,Nb.x,N.x,Nt.y,Nb.y,N.y,Nt.z,Nb.z,N.z);
}

vec3 NewSample() {
    vec2 v=noise();
    float theta=sqrt(v.x);
    float phi=2.*PI*v.y;
    float x=theta*cos(phi);
    float z=theta*sin(phi);
    return vec3(x,z,sqrt(max(0.,1.-v.x)));
}

float Shadow(vec3 pos, vec3 ray, float dist) {
    for (int i=0; i<2; i++) {
        float d=sphere(pos,ray,Spheres[i]);
        if (d>0. && d<dist) return 0.;
    }
    return 1.;
}

vec3 Light(vec3 cpos, vec3 norm, int rays) {
    vec3 Col=vec3(0.00001);
    //AreaLight
for (int i=0; i<NLights; i++) {
    mat3 lm=TBN(normalize(Lights[i].xyz-cpos));
    for (int r=0; r<rays; r++) {
        vec3 RandPos=Lights[i].xyz+(NewSample()*lm)*Lights[i].w;
        vec3 ltp=normalize(RandPos-cpos);
        float len=length(RandPos-cpos);
        float ll=max(0.,1.-len/5.);
        Col+=LColors[i]*max(0.,dot(norm,ltp)*Shadow(cpos,ltp,len)*ll);
    }
}
    return Col/float(rays);
}

vec3 background(vec3 dir) {
    return vec3(0.,0.7,1.)*0.7;//*max(dot(dir,vec3(0.,1.,0.)),0.);
}

vec3 Radiance(vec3 pos, in mat3 NM, vec3 PCol) {
    vec3 Color=vec3(0.); vec3 norm,sample,gcol,gpos,cpos; mat3 dnm;
    for (int i=0; i<GLSamples; i++) {
        sample=NewSample();
        sample=normalize(sample*NM);
        int gpr=Trace(pos,sample,gcol,gpos,norm);
        if (gpr==0) { Color+=PCol*background(sample); continue; }
        else if (gpr==2) { Color+=gcol; }
        Color+=PCol*gcol*Light(gpos,norm,1);
    }
    return Color*IGLS*0.5;
}

void main() {
    vec2 uv=(vt*2.-1.)*vec2(Aspect,1.);
    vec3 Ray=normalize(vec3(uv,1.)*Eye);
    //vec3 eye=vec3(Eye[0][2],Eye[1][2],Eye[2][2]);
    vec3 Final=vec3(0.);
    vec3 PPos,PCol,PNorm;
    int pr=Trace(Position,Ray,PCol,PPos,PNorm);
    if (pr==0) gl_FragColor=vec4(background(Ray),1.);
    else if (pr==2) gl_FragColor=vec4(PCol,1.);
    else {
        vec3 Pixel=PCol*Light(PPos,PNorm,4);
        Final+=Pixel*0.4+Radiance(PPos+PNorm*0.001,TBN(PNorm),PCol);
        gl_FragColor=vec4(Final.xyz,1.);
    }
}
]]

Nice program. Runs OK on my iPad Air.

@MMGames - I tried it on my Air2 and found it puzzling. I touch the (all white) screen several times and nothing happens. Then a blue dot zooms onto the screen and starts moving in the opposite direction to my touches.

Is that what is supposed to happen, and if so, what is it for?

EDIT - I had the sidebar with parameter turned off because I added it to the right of another tab. When I move the parameter, the blue dot whizzes onto screen and into the distance. What is it about? I can’t see any 3D or lighting effects although there seems to be code for them.

@Ignatz When I run it, I’m looking inside a box with 2 balls sitting on the floor. There’s a white light source on the back wall causing shadows from the balls. The screen moves in the opposite direction of my finger movement. There’s also a moving parameter that lets me set a speed to move around the box.

http://www.youtube.com/watch?v=zvqEcB-3_Tc

@ignatz That’s nothing like what I see. Have you tried reloading the code. Like I said, I’m inside a 3D box with 2 balls on the floor. I can move around the box and change the direction I’m looking at.

I just re-copied the code.

I’m using the last public Codea release 2.3.2

Here’s a video of the program if anyone else is having a problem.

https://youtu.be/hGY_S5P8M2c

Your photo app has an option to upload to YouTube straight from there

@Ignatz I wonder if there’s any other problems with code between the iPad Air 1 and the Air 2.

That’s a shader problem I think

I don’t have issues with other 3D or shader programs

It’s very odd that mine doesn’t work. Can you just check your Codea version, mine is 2.3.2(61)

That’s the version I’m on and the latest ios 9.2.1

Me too, hmm, cold reboot doesn’t help.

So the only obvious difference is in iPad versions, which is minor and shouldn’t have any effect

I haven’t done much with shaders, so I don’t know what’s causing the problem.

This is awesome! Also an iPad Air 1 here, it runs very well. I love the graininess.