Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

3D rendering on Codea

2»

Comments

  • SimeonSimeon Admin Mod
    Posts: 4,356

    Wow - those screens look amazing, @Xavier.

    I've added the operator support to vec3. This is our thought for the 3D API, would this help you at all?

    • vec3, vec4 and matrix types
    • Support for vec3 * matrix, vec4 * matrix operations
    • Update mesh() to use 3D vertices (vec3)
    • Add applyMatrix(m) and loadMatrix(m) functions. These multiply the given matrix against the current transform matrix.
  • Posts: 88

    @Simeon,
    It would be great if you could also implement a fast routine to transform 3D coordinates to 2D screen pixels. This is something needed all the time when dealing with 3D. If you could also implement a routine to detect hidden lines, that would be great.

  • beebee
    Posts: 377

    @Simeon: Please, don't forget the 2D things. Codea still lacks many things on 2D space. Don't let this 3D guys distract you. :D For example... more 2D shapes (round rectangle, arc, pie, polygon, flood fill, outlined text, etc) and API for gestures. ;)

  • SimeonSimeon Admin Mod
    Posts: 4,356

    @CrazyEd — Codea actually renders in 3D natively. So by simply giving mesh the ability to use 3D vertices, and the user the ability to load an arbitrary transformation matrix (such as perspective) the 3D -> Screen transform will happen via OpenGL.

    @bee we won't forget about 2D stuff. 3D is relatively simple to implement at the basic level, so it's easy for us to do. This is because Codea is already rendering with OpenGL.

  • beebee
    Posts: 377

    Thank you, @simeon. Please, provide polygon and floodfill on the next update. It's because physics engine already supports polygon object but the drawing API doesn't support it. :)

  • Posts: 88

    Thanks, @Simeon. That sounds very good. Then I will continue updating my 3D math code ...

  • SimeonSimeon Admin Mod
    Posts: 4,356

    @bee the mesh() class allows for arbitrary polygons to be created quite easily. You need to use triangulate() to generate the triangles, then dump them into a mesh to render a polygon. In fact, the initial reason mesh() was added was to support drawing the polygonal bodies used in the physics engine.

    I'm a bit lost on how floodfill would work — do you mean for images? Something like image:floodfill( x, y, color [, tolerance] )?

  • beebee
    Posts: 377

    @simeon: I thought mesh() is only able to create polygon with numbers of side are multiple of 3, no? How to define its stroke color? and the fill color? I haven't play with mesh() myself yet. I'll take a look its capability. Thank you for the hint. About floodfill, yes that's the way I thought it should work. But it could also work on screen as well.

  • SimeonSimeon Admin Mod
    Posts: 4,356

    @bee flood fill is not technically possible on the screen. But you could use the image variety by rendering into an image and then using flood fill on the image.

    Mesh can create any polygon, however the polygon must be broken into triangles. The triangulate() function does this. Mesh respects the current fill colour unless you set colours for individual vertices, which then get blended across the mesh.

    Mesh can also be filled with an image by setting its texture property and assigning texture coordinates to each vertex. The image will be multiplied against the fill or vertex colours, in the same way that tint() modifies a sprite.

    Mesh doesn't support a stroke, however this can be solved at the moment by rendering lines around the perimeter of the mesh.

  • beebee
    edited March 2012 Posts: 377

    @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. :)

  • edited March 2012 Posts: 196

    @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 :)

    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

  • SimeonSimeon Admin Mod
    edited March 2012 Posts: 4,356

    @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)

  • Posts: 196

    @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 :P

    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 :P

  • Posts: 2,160

    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.

  • edited March 2012 Posts: 196

    @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 :)

    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

  • Posts: 2,160

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

  • Posts: 196

    @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 :)

  • Posts: 2,160

    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

  • Posts: 2,160

    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.

  • edited March 2012 Posts: 196

    @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 ;)

    I'm actually working on endless terrain myself right now :D
    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 :/

    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 :(

    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 :(

    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 :D

    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 :(
    I'm super screwed :P

    edit: tab space ftl... wasn't done :D
    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 ;)

    Cheers,

    Xavier

  • edited March 2012 Posts: 196

    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

  • Posts: 2,820

    You got me excited...

  • edited May 2012 Posts: 196

    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 :D

    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 :P

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

    Cheers,

    Xavier

Sign In or Register to comment.