Img:set in Codea4

In Codea4 how does one do the equivalent of img:set(i,j,thiscol) in Codea3?

@John is there an img:set or equivalent in codea v4?

Sorry, I missed this question. There is but it works a little differently than in V3

You have img:setPixel(x, y, color) and img:setPixel(x, y, r, g, b, a) as well as img:setValue(x, y, r, g, b, a)

Why the distinction between setPixel() and setValue()? setPixel() assumes values are between 0 and 255 while setValue() just sets the data directly. To better match how modern GPUs work and to simplify the engine, you need to call img:apply() when you are finished setting values on your image to have them show up. You can also use img:readback() to get data back so you can use getPixel(x, y) and getValue(x, y) on the latest data. You can also use:

-- Modern
-- Create a random noise image using image:setForEach()

function setup()
    img = image(255, 255)
    img:setForEach(function(x, y)
        return math.random(), math.random(), math.random(), 1
    end)
    img:apply()    
end

function draw()
    background(40, 40, 50)
    sprite(img, WIDTH/2, HEIGHT/2)
end

And here’s an example of reading back some image data generated by a shader:

-- Modern

function setup()
   print("Hello World!")
    
   -- The code below is a bit of overkill but I wanted some noise values
   img = image(512, 512)
   context.push(img)
      -- Use a shader to draw noise values to the image
      background(shader.builder():unlit()
      :include "codea/noise/noise.glsl" -- include built-in GPU noise library
      :material[[
         float n = Perlin2D(getUV0() * 10) * 0.5 + 0.5;
         material.baseColor = vec4(n, n, n, 1);
      ]]
      :build())
   context.pop()
   img:readback(true) -- use true to perform readback immediately (normally it takes a frame)
   print(img:getValue(255, 255))
end

function draw()
    background(40, 40, 50)
    sprite(img, WIDTH/2, HEIGHT/2)    
end

Why is it so different? The use of apply and readback is an attempt to reflect what’s actually going on. The V3 version kept causing problems as it tries to automatically transfer data back and forth based on the use of set and get functions. This ended up causing several scenarios where users would cause huge performance hits without realising it since they didn’t know that reading and writing data to the GPU requires the entire image to be sent back and forth each time

2 Likes

@John - thanks for the info. I’ve used the V3 version to build points/pixels into an image then used the image as a sprite. Didn’t ever think of using it in any form of animation, thought it would be too hard on the pad hardware.

Looks like in V4 it will be more powerful - does this require metal?

Thanks again.

@John thanks that works.

@John are you sure the texture is mapped correctly to a sphere? There is a black line at the join of the left and right edges of the texture.

Actually, in v3 it didn’t work correctly either with the native spheres. I was obliged to use @LoopSpace ’s pseudomodel sphere to get a correct wrapping.

Hmm, do you have a screenshot example or some simple code to show this?

@John here is my test code:


-- 3d shadows

function setup()
    
   domImage=makeDomImage()

 --   domImage=image.read(asset.builtin.Basic.Icon)
    scn = scene.default3d()
    scn.sky=color(80, 125, 233)
    scn.sky=nil
    local rig = scn.camera:add(camera.rigs.orbit)
    rig.angles.x = 45
    rig.angles.y = 45
    
    scn:entity():add(mesh.plane(10,10))
    
    local sphere1 = scn:entity()
    sphere1:add(mesh.sphere(2))
    sphere1.y = 2
    sphere1.material.map=domImage
    
    local sphere2 = scn:entity()
--    sphere2:add(mesh.sphere(1))
    sphere2:add(mesh.sphere(2))
    sphere2.y = 5
    sphere2.x= 3
    sphere2.material.map=domImage
    
    scene.main = scn
    
--    parameter.number("sx",0,100,3, function() sphere2.x=sx end)
end

function makeDomImage()
    -- print("makedom ",pmtcol)
    local i,j
    --    local img=image(255,255)
    --    local img=image(128,128)
 --  local img=image(512,512)
   local img=image(1024,1024)
    mushroom_size = 0.23
    pmt_size      = 0.05
    ring_size     = 0.07
    
    
    twelveth  = 0.083333
    sixth     = 0.1666667
    
    p2 = pmt_size * pmt_size
    r2 = ring_size * ring_size
    print ("img.w, h",img.width,img.height)
    for i=1,img.width do
        for j=1,img.height do
            --           print(pmtcol)
 --           local col=vec4(0,0,0,0)    --initially transparent
            local col=vec4(1,1,1,1)    --initially transparent
            local phi_prime = i/img.width
            local theta     = j/img.height
            local up        = 0.0
            
            if theta < 0.5  then --force theta > 0.5
                phi_prime = phi_prime+twelveth
                theta = 1.0-theta
                up = 100.0
            end
            
            phi_prime = math.fmod( phi_prime , sixth )
            if phi_prime > twelveth then phi_prime = sixth - phi_prime end
            
            local a =math.sin( theta * 3.1415 )
            a = a*a*4.0
            
            local A = a*phi_prime * phi_prime
            local tminusp=twelveth - phi_prime
            local B = a * tminusp*tminusp
            
            --the theta's of the dom positions are:
            -- 0.980875, 1.2706, 1.872738, 2.162463, 2.579597, 3.1415923073180982
            
            local omt=  1.0-theta
            local d1 = omt*omt
            
            local tm8= theta-0.82111139
            d1 =math.min( d1, up + A + tm8*tm8 )
            
            local tm7= theta-0.687549
            local d1 = math.min (d1, B + tm7*tm7 )
            
            local tm6= theta-0.59587
            d1 =math.min( d1, A + tm6*tm6 )
            
            --           if ( d1 < r2 ) then  col = vec4( 0.95, 0.8, 1.0, 1.0 ) end  --ring
            if ( d1 < r2 ) then  col = vec4( 0.5, 0.5, 0.5, 1.0 ) end  --ring
            
            if ( d1 < p2 ) then --pmt
                --                print(pmtcol)
                --              col=vec4(pmtcol.r,pmtcol.g,pmtcol.b,pmtcol.a)
                col = vec4( 0.5, 0.3, 0.1, 1.0 )
            end   --pmt
            --            if ( d1 < p2 ) then  col = pmtcol end   --pmt
            
            if ( j/img.height < mushroom_size ) then col = vec4( 0.5, 0.5, 0.5, 1.0 ) end --mushroom
--            print(col[1])
            col[1]=math.floor (col[1]*255)
            col[2]=math.floor (col[2]*255)
            col[3]=math.floor (col[3]*255)
            col[4]=math.floor (col[4]*255)
            thiscol=color(col[1],col[2],col[3],col[4])
 --                      print(i,j, thiscol)
            img:setPixel(i, j, thiscol) 
        end
    end
    img:apply()
    return img
end

@John just wondering if you got a chance to look into this?

I tried your example and managed to fix it by using img:setPixel(i-1, j-1, thiscol), so it’s just the function not correctly offsetting for Lua style indices, leaving a column of black pixels (default values) which causes the apparent seam

I’ll put a fix in for this in the next patch

@John that fixed it-thanks

1 Like

@John the sphere mesh seems to have ‘waves’ in the image. Any way to smooth that away?

I think this is due to shadow mapping issues, which can be adjusted via some settings

You can get the shadow settings object via scn.sun:get(light).shadowOptions

        shadowOptions["size"] = &Light::ShadowOptions::mapSize;
        shadowOptions["constantBias"] = &Light::ShadowOptions::constantBias;
        shadowOptions["normalBias"] = &Light::ShadowOptions::normalBias;
        shadowOptions["far"] = &Light::ShadowOptions::shadowFar;
        shadowOptions["nearHint"] = &Light::ShadowOptions::shadowNearHint;
        shadowOptions["farHint"] = &Light::ShadowOptions::shadowFarHint;
        shadowOptions["polygonOffsetConstant"] = &Light::ShadowOptions::polygonOffsetConstant;
        shadowOptions["polygonOffsetSlope"] = &Light::ShadowOptions::polygonOffsetSlope;

Shadow mapping is very complicated and fussy with various settings. A lot of engines have these settings but generally have sensible defaults. I may not have set everything up correctly or might be missing stuff. The above are the settings exposed via the API and so you can check what they are and try some different values to see if you can fix the issue. The most important settings for this particular problem would be constantBias and normalBias which effect which regions are considered to be in shadow based on the unprojected shadow map values

@John my first try of constantBias=1 and normalBias=1 seems to fix the issue. As parameter.number does not work in V4, I did not explore other settings.

1 Like

Cool. Hopefully we’ll have all the parameter stuff working soon