Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]

Fur / foliage WIP
Started by StarCrunch Apr 10 2017 08:11 PM

4 replies to this topic
fur foliage shells
[TOPIC: post.html]


[GLOBAL: userInfoPane.html]
  • Contributor

  • 605 posts
  • Enterprise

A fairly common way to do fur, foliage, and other stuff based on densities is to simply alpha-blend several shells together. (A nice overview is found here.) This has been nagging at me for some time, so I gave it a shot a few days ago.


My current results (N.B. uses the memory bitmap plugin for the density mask):

local function RoundUpMask (x)
  local xx = x + 13 -- 3 black pixels per side, for mask, plus 7 to round
                    -- all but multiples of 8 past the next such multiple

  return xx - xx % 8 -- Remove the overshot to land on a multiple

local Dim = 128
local MaskDim = RoundUpMask(Dim)
local Diff = (MaskDim - Dim) / 2

local memoryBitmap = require("plugin.memoryBitmap")

local mask = memoryBitmap.newTexture{ width = MaskDim, height = MaskDim, format = "mask" }

local iota = {}

for i = 1, Dim^2 do
  iota[i] = i

local n = #iota

for i = 1, math.ceil(.125 * #iota) do
  local j = math.random(n)
  local index = iota[j]

  iota[j], iota[n], n = iota[n], iota[j], n - 1

  local x = (index - 1) % Dim + 1
  local y = (index - x) / Dim + 1

  mask:setPixel(x + Diff, y + Diff, 1, 1, 1)

local omask = graphics.newMask(mask.filename, mask.baseDir)

local function DataAndVertex (kernel)
  kernel.vertexData = {
    { name = "cx", index = 0 },
    { name = "cy", index = 1 },
    { name = "inclination", index = 2, min = 0, max = 90, default = 12.5 },
    { name = "taper_to", index = 3, min = 0, max = 1, default = 1 }

  kernel.vertex = [[
    P_POSITION vec2 VertexKernel (P_POSITION vec2 pos)
      P_UV vec2 diff = pos - CoronaVertexUserData.xy;

      diff.x *= mix(CoronaVertexUserData.w, 1., CoronaTexCoord.y);
      // diff.y *= sin(radians(CoronaVertexUserData.z));

      return CoronaVertexUserData.xy + diff;

-- Generator
-- Filter

local k = { category = "filter", name = "incline" }



for layer = 1, 3 do
  local objects = display.newGroup()

  local N = 50

  for i = 1, N do
    local x = display.contentCenterX
    local object = display.newRect(objects, x, display.contentCenterY - i * 1.75, display.contentWidth, 70)

    object.m_y = object.y


    object.maskScaleX, object.maskScaleY = object.width / Dim, object.height / Dim

    -- can adapt this, e.g. for fur
    local g = 1 - (i - 1) / N * .57
    local r, b = 0, 0

    object:setFillColor(r, g, b, 1 - (i - 1) / N)

    object.fill.effect = "filter.custom.incline"

    object.fill.effect.cx = object.x
    object.fill.effect.cy = object.y
    object.fill.effect.inclination = 45
    object.fill.effect.taper_to = .37

  local prev

  Runtime:addEventListener("enterFrame", function(event)
    local now = event.time
    local diff = (prev and now - prev or 0) / 1000
    local xprev = objects[1].x
    local wind = math.random(30, 45)

    for i = 2, N do
      local d = 13.5 * (i / N) * diff
      local x = xprev + d * (2 * math.sin(event.time / 200 + i / N + layer * 2.7) - 1)

      x = math.max(xprev - 3.5, math.min(xprev + 3.5, x))

      local obj = objects[i]

      obj.x = x
      obj.y = obj.m_y + wind * (i / N)^3

      xprev = x

    prev = now

It's a bit of a mess at the moment, with cut-and-paste artifacts, lots of magic numbers, and such, but demonstrates something like windy grass. I have vague ideas how I might contort this to work with a normal map, but it's a long way off... that's ultimately what I'm going for, though, e.g. to cover animal sprites with fur.


Anyhow, just throwing it out there.

  • rakoonic, sporkfin and ingemar_cl like this

[TOPIC: post.html]


[GLOBAL: userInfoPane.html]
  • Contributor

  • 189 posts
  • Corona SDK

Brilliant!  Thanks for sharing.  Would you try to build normal maps on the fly by adjusting screen captures and turning them into png's?

[TOPIC: post.html]


[GLOBAL: userInfoPane.html]
  • Contributor

  • 605 posts
  • Enterprise



Straight-up screen captures would be pretty expensive, especially sending them to a file in between, so I'm not leaning that way. (I did make some middling progress trying to capture raw screen bytes, after some investigations in response to a PM, but the straightforward way only seems to work on desktop. The rest are rather onerous. :( This would let me populate bytemaps, avoiding file IO if nothing else, and possibly do some of the legwork on the CPU, e.g. with operations in impack.) Maybe a compromise solution with memory mapping would work, though I've not explored that.


The shells technique builds on having layered geometry, rather than per-pixel ops like a normal map, so in practice it's probably going to be using a downsampled map. I'm leaning toward a grid-type mesh with an averaged normal per grid cell. On some hardware it's possible to read the texture during the vertex half of shading, where moving these around would then be fairly basic; failing that, it might be more a matter of overestimating the cell area and then filtering out superfluous pixels. Corona doesn't yet expose this feature's availability (maybe one could write a shader that will break without it, then test if it could be successfully assigned; this would produce some noise in the console on failure, though), so maybe I'd make a small plugin for info like this.


More generally, where I don't have some nice well-known shape, I'd probably go some heuristic route, depending on cases like whether the object has any inner edges (think of the Sobel, wood cut, and to some extent the emboss filters from among the built-ins). When it doesn't have any strong edges, a poor man's heightmap could probably be built up following the ideas from this other recent thread, by layering scaled versions of the object on top of itself. That will also break down, of course, or at least not look perfect, if there are holes or twists and turns along the boundary. For some of the other cases I'm at least thinking along the lines of signed distance fields (e.g. see some of the objects in figure 34-15 here), though I don't know what performance would be like nor whether they handle interior edges.


Anyhow, most of this is all still in idea land. :P  

[TOPIC: post.html]


[GLOBAL: userInfoPane.html]
  • Contributor

  • 189 posts
  • Corona SDK

I appreciate your posts, you reference everything and I always learn something new.  When I started programming with Corona I went back an relearned trigonometry - loving it the second time around, largely because it finally had a purpose.  Looking into your suggested reading on signed distance fields makes me think I should relearn calculus and enjoy it this time.  Maybe someday; for now, I'll follow your adventures  :D

  • StarCrunch likes this

[TOPIC: post.html]


[GLOBAL: userInfoPane.html]
  • Contributor

  • 605 posts
  • Enterprise

Thanks again. Your own project and basiliogerman's were actually part of what prompted me to mention the normal map here.  :) I've also got a few other effects that build on them. They're often difficult to write, owing to the (current?) two-texture limit. (This is true in a number of cases, really.)


On the subject of distance fields, I've wrapped up Mikko Mononen's sdf implementation in impack, though sadly I have yet to do anything interesting with it. I also had a quick go with a different one a while back, though at the time it didn't have a very open license, so I didn't do too much. Both projects refer back to white papers, incidentally.


I have delusions about eventually finishing some helpful mathematical material ("Circles" and "Centers" are basically trig), but I'm rather snagged.  :P