3D rendering on Codea

@simeon: I see. Mesh looks pretty advance and interesting. I’ll take a look at it. A simple sample code, for example draw a star using mesh, will be much appreciated. Thank you. :slight_smile:

Here you go, @beehttp://twolivesleft.com/Codea/Talk/discussion/734/star-mesh

@simeon - Oh wow yes that would be fantastic !
3D rendering can be scary for beginners, but having it all wrapped inside Codea will make the whole concept easier to grasp I’m sure :slight_smile:

Btw, as I was replying to your post I was playing with vectors, and I have a quick question regarding the speed of vec2 and vec3 classes

Any reason why using the built in vec2/vec3 class is so much slower than making your own (although vec2/vec3 initialisation is obviously faster since it’s built in)?

v3 = class()

function v3:init(x, y, z)
    self.x = x
    self.y = y
    self.z = z
end

For example, using custom v3 instead of vec3 is around 7-8 times faster

local v = v3(0,0,0)
for i=1, 100000 do
    v.x = i
    v.y = i
    v.z = i
end  

Like I said, the same goes for vec2. I’m thinking about rewriting some of my code but I’m wondering if it’s not just a bug and not an actual limitation ?

Cheers,

Xavier

@Xavier I suspect that assignment, such as v.x = 4 is slower than a Lua class, but operations are faster. For example:

v1 = vec2(1,2)
v2 = vec2(3,4)

v = v1 + v2 

Will be much faster than writing your own “add” function that adds two vectors.

I tried the following benchmark:

v2 = class()
function v2:init(x,y)
    self.x = x
    self.y = y
end
function v2:add(v)
    return v2( self.x + v.x, self.y + v.y )
end

function setup()
    local vn = vec2(1,1)
    local vc = v2(1,1)
    
    local startTime = os.clock()
    
    for i = 1,1000000 do
        --vc.x = i
        --vc.y = i
        vc = vc:add(vc)
    end
    
    print("Time taken for custom vec2: ".. (os.clock() - startTime))
    startTime = os.clock()
    
    for i = 1,1000000 do
        --vn.x = i
        --vn.y = i
        vn = vn + vn
    end
    
    print("Time taken for native vec2: " .. (os.clock() - startTime))
end

function draw()
end

Time taken for custom vec2: 3.05869

Time taken for native vec2: 1.09686

As you can see, native is about 3 times faster than the custom class. However for assignment, you are correct, the native class is slower. I will have to look at ways to fix this.

(Edit: I ran these benchmarks on my desktop, not on an actual iPad, so these times will be much faster. But the differences should be similar)

@Simeon - Yop I get similar results on device.
I changed my code to use a custom vec2 class, and it is indeed much faster (8fps gain with 12k+ points) since I’m not doing any operations with them, just storing x,y values.

The catch is that I can’t draw since mesh.texture only takes an array of native vec2, so can’t use an array of custom vec2 :stuck_out_tongue:

Its not that big of a deal for me since I reached my fps goal already, I just thought it might be a glitch so I brought it up.

However If you ever find a way to make vector assignment faster, then all the better :stuck_out_tongue:

Isn’t it quicker to do v = vec2(10,10) instead of v = vec2(0,0) v.x = 10 v.y = 10?

Regarding vector and matrix implementation, I really, really hope that you’ll multiply matrices on the left. Otherwise you’ll make life very hard for people as that’s the expected way around. I have recently written lua classes for vectors and matrices of arbitrary size and can let you have them if it would be of any use. I’ve been working on a little app to show how a 2x2 matrix acts on the Euclidean plane - but got distracted by number spinners.

When I was playing with Xavier’s code then the biggest influence on speed was the sorting (I still claim that you only need to do the z-sorting when you create the grid). It’s the same for me with my 3D shape explorer. If this could be done natively, it would be great. In looking at speed savings I learnt more about algorithms than I feel happy knowing. In particular, for both situations there’s an initial sort but then after that, the data is only slightly perturbed so the sort doesn’t have to do much. As I understand it, quicksort performs badly under these circumstances when compared to, say, a merge sort. But the time taken to set up and tear down a merge sort makes it impractical to implement in pure lua - at least, I didn’t get a good response time from it. Bucket sorts would also be useful as one can use them to sort stuff as it is generated, rather than having to generate it and then sort it.

@Andrew_Stacey - No it’s still faster to do v.x, v.y assignments than to create a new vec2, but not by much (which is weird)

As for the zsorting, I indeed need it when I first display the model, but I also need it when it rotates. I think thats what you mentionned in an earlier post ( when adding vertices and when rotating).
Like you pointed out, my code currently also does it when it’s not needed, planning on fixing that :slight_smile:

Edit: It is my understanding that once Codea supports vec3 for meshes, it will use the zbuffer, but only for its own polygons. Meaning that drawing multiple meshes won’t sort them out, but a single mesh wouldn’t have any z error artifacts.

Thats is if each mesh is some independant render to texture object, which is then drawn into the Codea buffer. Im probably be wrong though, as I’m not sure how it’s setup

Actually, if you handle the rotation correctly then you don’t have to do it then either.

@Andrew_Stacey - mmm I’m actually not using proper wording here. The rotation in my app is fake, it’s just translation (the pseudo sphere gives it a rotation effect).

I’m only projecting vertices to screen, not doing any rotation. Could you clarify if your point still stands ? If so and if you have the time, I’m interested in reading more :slight_smile:

I know, and that’s how you get to do the saving.

Your data is arranged in a lattice. When you translate a lattice by a whole unit then it looks just like the original lattice again. So you can exploit this since then the ordering required by the new positions are the same as the orderings of the old positions but reassigned to different vertices.

I’m not explaining this very well!

Model = class()

function Model:init(
        quality, 
        n, 
        dist, 
        wrapping, 
        str,
        Red,
        Green,
        Blue,
        wireframe,
        texture
        )
    self.quality = quality
    self.nPower = n
    self.dist = dist
    self.wrapping = wrapping
    self.str = str
    self.Red = Red
    self.Green = Green
    self.Blue = Blue
    self.wireframe = wireframe
    self.texture = texture
    self.x = 0
    self.y = 0
    self.mesh = mesh()
    self:create()
    self:generate()
    self:colour()
    self:setwireframe()
end

function Model:update(
        quality, 
        n, 
        dist, 
        wrapping, 
        str,
        Red,
        Green,
        Blue,
        wireframe
        )
        local recreate, regenerate, recolour, rewire
    if quality ~= self.quality then
        recreate = true
        regenerate = true
        recolour = true
        rewire = true
    end
    if n ~= self.nPower
        or dist ~= self.dist
        or wrapping ~= self.wrapping
        or str ~= self.str
            then
        regenerate = true
        recolour = true
    end
    if Red ~= self.Red
        or Green ~= self.Green
        or Blue ~= self.Blur
        then
            recolour = true
    end
    if wireframe ~= self.wireframe then
        rewire = true
    end
    self.quality = quality or self.quality
    self.nPower = n or self.nPower
    self.dist = dist or self.dist
    self.wrapping = wrapping or self.wrapping
    self.str = str or self.str
    self.Red = Red or self.Red
    self.Green = Green or self.Green
    self.Blue = Blue or self.Blue
    self.wireframe = wireframe or self.wireframe
    if recreate then
        self:create()
    end
    if regenerate then
        self:generate()
    end
    if recolour then
        self:colour()
    end
    if rewire then
        self:setwireframe()
    end
end

function Model:create()
    local numSteps = math.pow(2,6-self.quality)
    
    local t = {}
    local hn = (numSteps)/2
    for i = 1,numSteps do
        for j = 1,numSteps do
            table.insert(t,{(i-hn)^2 + (j-hn)^2,i,j})
        end
    end
    table.sort(t,function(a,b) return a[1] > b[1] end)
    self.grid = t
    self.numSteps = numSteps
end

function Model:generate()
    local n = self.nPower
    local dist = self.dist
    local wrapping = self.wrapping
    local str = self.str
    local f = {}
    local ver = {}
    local vv,x,y,z,s,col,ix,iy
    local g = self.grid
    local numSteps = self.numSteps
    local gridSize = 512/numSteps
    local hn = numSteps*gridSize/2
    local sx = self.x or 0
    local sy = self.y or 0
    local isx = math.floor(sx/gridSize)*gridSize
    local isy = math.floor(sy/gridSize)*gridSize
    local offsetX = WIDTH*.5
    local offsetY = HEIGHT*.5
    for i = 0,numSteps do
        vv = {}
        ix = i*gridSize - isx
        for j = 0,numSteps do
            iz = j*gridSize - isy
            x = (ix - hn + sx)*wrapping
            z = (iz - hn + sy)*wrapping
            y = dist - noise(ix/n, iz/n)*str
            y = math.max(y,100)
            y = y + (x * x + z * z)*0.001
            s = 600/(600 + y)
            col = 255 - y*.075
            table.insert(vv,{
                vec2(x * s + offsetX,z * s + offsetY),
                col
                })
        end
        table.insert(ver,vv)
    end
    local meshver = {}
    local meshtex = {}
    local numver = 0
    local tri = {
        {
            {0,0},
            {1,0},
            {1,1},
            {0,0},
            {0,1},
            {1,1}
        },
        {
            {0,0},
            {0,1},
            {1,1},
            {0,0},
            {1,0},
            {1,1}
        }
        }
        local i
    for k,v in ipairs(g) do
        if v[2] > v[1] then
            i = 2
        else
            i = 1
        end
        for l,u in ipairs(tri[i]) do
            table.insert(meshver,ver[v[2]+u[1]][v[3]+u[2]][1])
            table.insert(meshtex,vec2(u[1],u[2]))
            numver = numver + 1
        end
    end
    self.numver = numver
    self.vertices = ver
    self.mesh.vertices = meshver
    
    self.mesh.texCoords = meshtex
end

function Model:setwireframe()
    if self.wireframe == 1 then
        self.mesh.texture = self.texture
    else
        self.mesh.texture = nil
    end
end

function Model:colour()
    local Red = self.Red
    local Green = self.Green
    local Blue = self.Blue
    local g = self.grid
    local ver = self.vertices
    local meshcol = {}
    local col
    local a = 0
    local tri = {
        {
            {0,0},
            {1,0},
            {1,1},
            {0,0},
            {0,1},
            {1,1}
        },
        {
            {0,0},
            {0,1},
            {1,1},
            {0,0},
            {1,0},
            {1,1}
        }
        }
    local i
    for k,v in ipairs(g) do
        if v[2] > v[1] then
            i = 2
        else
            i = 1
        end
        for l,u in ipairs(tri[i]) do
            col = ver[v[2]+u[1]][v[3]+u[2]][2]*a/self.numver
            a = a + 1
            table.insert(meshcol,color(col*Red,col*Green,col*Blue,255))
        end
    end
    self.mesh.colors = meshcol
end

function Model:shift(x,y)
    self.x = self.x + x/self.wrapping
    self.y = self.y + y/self.wrapping
    
    self:generate(self.nPower, self.dist, self.wrapping, self.str)
    self:colour(self.Red,self.Green,self.Blue)
end

function Model:draw()
    self.mesh:draw()
end


function setup()
    


  iparameter("quality", 1, 4, 3)


  iparameter("str", 0, 2048, 512)


  iparameter("nPower", 10, 200, 100)


  parameter("wrapping", 1, 20, 4)

-- distance from camera
  iparameter("dist", 256, 2048, 512)

  iparameter("wireframe", 0, 1, 0)


  parameter("Red", 0, 1, 1)
  parameter("Green", 0, 1, 0.3)
  parameter("Blue", 0, 1, 0.2)
    local w = 64
    wire = image(w,w)
    setContext(wire)
    pushStyle()
    resetStyle()
    strokeWidth(2)
    noSmooth()
    stroke(255, 255, 255, 255)
    fill(255, 204, 0, 134)
    rect(1,1,w,w)
    strokeWidth(4)
    line(1,1,w,w)
    popStyle()
    setContext()
    model = Model(
        quality, 
        nPower, 
        dist, 
        wrapping, 
        str,
        Red,
        Green,
        Blue,
        wireframe,
        wire
        )
    fps = {}
    fpsn = 100
    for i = 1,fpsn do
        fps[i] = 60
    end
    fpsi = 1
end

function draw()
    background(0, 0, 0, 255)
    fps[fpsi] = 1/DeltaTime
    local tfps = 0
    for i = 1,fpsn do
        tfps = tfps + fps[i]
    end
    fpsi = fpsi%fpsn + 1
    textMode(CORNER)
   text("FPS: "..math.floor(1/DeltaTime*10)/10, WIDTH - 200, HEIGHT - 30)
    text("FPS: "..math.floor(tfps/fpsn*10)/10, WIDTH - 200, HEIGHT - 60)
    text("Vertices: "..model.numver, WIDTH - 200, HEIGHT - 90)
    text("Double tap for Fullscreen", WIDTH/2, 30)
    
    model:update(
        quality, 
        nPower, 
        dist, 
        wrapping, 
        str,
        Red,
        Green,
        Blue,
        wireframe
        )
    
    model:draw()
end

function touched(touch)

   if touch.state == BEGAN and touch.tapCount == 2 then
       if fullscreen then
           displayMode(STANDARD)
           fullscreen = false
       else
           displayMode(FULLSCREEN)
           fullscreen = true
       end
    model:update()
   else
   model:shift(touch.deltaX,touch.deltaY)
   end

end

end

Ran out of space.

I’m sufficiently bad a programmer than in order to understand what you were doing then I had to rewrite it.

I haven’t ironed out all of the wrinkles - I still get some edge effects. Also, it’s based on a previous version of your code - before you started playing with textures.

@Andrew_Stacey - Nice work ! I don’t think you’re a bad programmer, and I don’t think it’s ever a bad thing to turn someone-else’s messy code into your own to better read it :wink:

I’m actually working on endless terrain myself right now :smiley:
I do it by shifting the noise values on movement, then regenerating the entire mesh/texture.
It works but isn’t efficient, because texture generation is so taxing. I’m therefore forced to reduce the texture quality when rotating if I want to keep it smooth, and that’s not acceptable :confused:

My idea was to use the something similar to geometry clipmaps or a tile based game where you crop only rows/colomns and recalculate new rows/colomns based on movement, not needing to calculate the rest.

The principle is cool and it looked great in my dumb head, but implementing is is proving to be tricky due to texture generation speed :frowning:

Subdividing my terrain into tiles and adding/removing tiles when needed works, but the speed issue remains: I still have to render a fair amount of texture area, or have way too many tiles which slows it down.
I think I’m screwed :frowning:

As for the order, that’s really cool. It works great because you’re always “centered” on your grid, so (i-hn)^2 + (j-hn)^2 where hn = (numSteps)/2 is a functional way to get distance from eye to cell. Really really cool way to get rid of sorting, love it :smiley:

Unfortunately, if I wanted to use the same algorithm, I’d have to recalculate the grid on movement like you do, but also the textures. Like I said earlier on, I haven’t found a way to do that efficiently with textures at a reasonable quality :frowning:
I’m super screwed :stuck_out_tongue:

edit: tab space ftl… wasn’t done :smiley:
Well if I find a way to make the perlin noise seamless, then the problem goes away, since I can just reuse the tiles of textures, which means I only need them rendered once.

Else, I’m just going to have to wait for Codea custom textures to make a tiled heightmap :wink:

Cheers,

Xavier

oh boy… New Codea version makes this run sooooooooooo fast it’s not right hahah.
I was hitting mid 30 fps with 4096 polygons, and boy I was proud.
After removing all the software z ordering and perspective projection, it’s now at 40/50fps with 32768 polygons…

Working on something cool

/cheers

Xavier

You got me excited…

thread necro inc !

Well I have soft shadows working in real time (as in montains casting shadows over other montains). Managed to get it to run quite fast using low res textures for the shadowmap and a very cool and very fast blur trick. Simulating a sunrise is very cool looking while having smooth framerate :smiley:

However since there is no multi texturing, it only runs smoothly when using low level terrain texture… The thing is that I need to blend the textures, so it gets super slow the higher resolution the terrain texture gets.

It’s actually much much much faster to just render the scene twice (once with terrain textures, and another time with transparent shadow texture), but it’s pretty lame :stuck_out_tongue:

Anyway, I’m pretty much done with this project for now, working on other things

Cheers,

Xavier