Geo-Scatter - 5.4 Scatter Groups

Hello and welcome to the Geo-Scatter community thread !

I am Dorian B. (aka bd3d), Founder & lead dev of the Geo-Scatter project, I personally read every single post here. Don’t hesitate to tag @carbon2 Lead dev of manual mode or myself @bd3d if you have questions :slightly_smiling_face:

We also have a new discord server now :point_down:


Get Geo-Scatter

Official Website

Twitter (news)

Instagram (art)


This topic is also a work in progress topic from the very beginning, you can scroll through Scatter original development starting at the posts below

Original First post:

Hello.
I spend 4 hours this morning working on this meadow (only shading and particles)
i just want to make a topic speaking about the subject

After reading this article here below tried my best to recreate this exact scene in blender.
https://cgtricks.com/making-meadow-darstellungsart/

And this is the result:

Important steps:

  1. have a bunch of different variety of grasses/leaves/clovers… ect
  2. make sure to have different models of your grass, some in clusters, in different sizes and radius.
  3. make sure the terrain is naturally displaced and not flat.
  4. use a grass texture with little roots and different tones of grasses for the base plane.
  5. make sure that the material and translucency of your grass is accurate
  6. the material of the grass need to be wet enoug. 0.4 roughness is perfect for this scene
  7. Distribute your differents grasses models with a cluster map like this below using procedural black and white texture
  8. Tweak the material of the grass with a procedural texture, so that you can have different shade of green/yellow/dead grass tones, get rid of uniform colors. For the aldebeo AND translucency.
  9. in your scene always have some shadow to give this grass some relief

and its done.

While addons like graswald or grass-essential do excellent work for the grass material and model, there’s still a lot of work to do for clustering the different species with various clustering maps, reworking the material for color variation, and a lot of features from other software scattering packages are missing.

I’d really love to see a professional scattering toolset within blender.

Edit*
so let’s do it !

81 Likes

I’m curious as to what you mean by ‘reworking the material for color variation’ because graswald does have things I would suggest are of that nature.

As for clustering, I have been thinking of a way to procedurally generate vertex groups that I think would be really powerful in controlling distributions. But being able to do things directly with textures is also powerful in it’s own right. I’ll definitely need to make a few scenes and experiment before I have a concrete idea of what options are good and how to integrate it into a system like graswald—because unless you can simplify the texturing system or provide new functionality, there is little point in copying the settings to a different panel imo.

But I like that you are bringing this up, it’s a good topic for discussion.

On that note, any settings you found particularly interesting?

4 Likes

Looks pretty good. Want to share grass translucency values (or ranges) you used in this shader?

2 Likes

I was speaking of this

I don’t remember graswald having’ this ? But it’s easy to do. The problematic Part is the automatic cluster distribution.

I already tried to do an automatic cluster distribution code based on pre created texture set on global. It’s a really simple code but I hope it can help you. I’ll share it later this evening.

Seems like a bad idea. An automatic creation of various cluster maps with a texture seems more volatile. It doesn’t have any geometrical requirements. Could be used for flat surface and be re-used once the texture is present in the scene.

1 Like

I meant for effects like: leaves cluster around tree objects, plants shy away from shade, etc: things that depend on other geometry in the scene.

Ok, I have an idea on how to automate that, I’ll explore it’s feasibility.

Graswald currently has a dead patches property which does that, but has few customization options

1 Like

If you’re incorporating this in graswald then look at how forestpack does this. They also use textures like BD3D suggests. The problem with vertex group clusters is that it will depend on a highpoly surface being populated with your clusters. Being able to populate it on every surface (single poly or highpoly) is a must, it should not be dependend of the polycount. It should work the same on every surface, same density of particles and same pattern, across all surfaces in your scene that have this cluster aplied to.

The example DB3D showed is very promising!

This workflow with grass patches would be awesome as it will be usable on bigger surfaces also. A bigger patch of grass in one particle will make the viewport more responsive. I’ve tested it and it works as a charm even on huge terrains.

Is it possible to have your cluster pattern detect edges? Like stop 40cm away from the edge of the surface ? In sketchup there is the skatter plugin who solves the problem of patches on big surfaces by dividing the grass in two particle system, one with a very small patch of grass (like 10cm circle) for the border of the lawn near the edge of the surface and then one for the rest with a bigger grasspatch (like 50cm large).

If you don’t do this you’ll either have grass patches overflowing in areas where there should be no grass or have jagged edges where grass is missing.

3 Likes

Sorry I looked back at it and it’s not the clusters but the distribution of the particles that are based on maps (I don’t know on what the clusters are based)

see this video
https://vimeo.com/60241623

I don’t use max myself so I only saw the feature from the videos or from the small trial I did a couple of months ago but from what I saw it’s super powerful and flexible. Look at the other features if you want some inspiration, it’s crazy how good that forestpack plugin is. I wish they made a blender version of it.

2 Likes

yes that’s what i meant by clustering the particles

Agree.

But i feel like it’s quite over complex for just clustering some vegetations ? A simple version could be done in blender with less of an headache.

1 Like

Like this one for exampe

https://www.youtube.com/watch?v=LLw_RTSTcVs

2 Likes

I really appreciate your effort guys, having distribution maps in Graswald would be so awesome.
A proxy system is what I am missing as well. You might want to check out this script which I use as a workaround for now

3 Likes

Really nice ! Could the vertex clouds have colors ?

1 Like

Okay, i got a tons of ideas for a scattering tool for blender.

Ideas like a custom homemade proxy system, automatic two click particles distribution with fully customizable parameters slots, if pablo remesher will be in the modifier stack soon I also could do an automatic terrain displacement + custom settings and slots.

This Could be a killer addon. I’ll start today.

1 Like

That’s my biggest problem with the script. While performance increase is surprisingly good, readability suffers with increasing amounts of particles. I’ve been searching a solution but that will most likely have to be hard coded in the ‘loose points’ draw code.

I have no idea how to achieve that:sweat_smile:

2 Likes

Maybe ask on the blender stack exchange ? :slightly_smiling_face:

1 Like

I did :slight_smile: but apparently it’s for questions about usage and python coding, not about blender’s source code. On devtalk I’m still hoping for someone to answer. Maybe I also give it a try in the developer chat :smiley:

Well, then let’s make it 100$ for a good response :joy:

As far as I know, the last proposal about a proper proxy system on right-click select was declined. Brecht mentioned that alembic and USD integration could handle that and much more but don’t ask me how.

About a week ago, I proposed point cloud display myself to have that at least.
If I’d know how I would try it myself.

3 Likes

I made my my own test to see how difficult or easy it is to make grass believable in blender, it turned out to be really easy if you don’t need a real close-up (for archviz images this is enough)

=> only one flat surface with a grass texture, one patch of grass, one material for the grass, no texture maps (just plain colors)

4 Likes

# "Scatter" Add-on
# Copyright (C) 2019 Dorian Borremans aka BD3D
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# <pep8 compliant>

bl_info = {
    "name" : "Scatter [BD3D]", 
    "author" : "BD3D",   
    "description" : "The scattering tool of 2.8",
    "blender" : (2, 80, 0),
    "location" : "Operator",
    "warning" : "",
    "category" : "Generic"
}

import bpy
import random
from bpy.types import Menu
import webbrowser
from bpy.types import Operator, AddonPreferences, PropertyGroup
from bpy.props import StringProperty, IntProperty, BoolProperty, FloatProperty
import rna_keymap_ui
context = bpy.context

def find_collection(context, item):
    collections = item.users_collection
    if len(collections) > 0:
        return collections[0]
    return context.scene.collection

def make_collection(collection_name, parent_collection):
    if collection_name in bpy.data.collections:
        return bpy.data.collections[collection_name]
    else:
        new_collection = bpy.data.collections.new(collection_name)
        parent_collection.children.link(new_collection)
        return new_collection

######################################################################################
######################################################################################
# # # # # # # # # # # #         SCATTER ADDON PREF             # # # # # # # # # # # # 
######################################################################################
######################################################################################

class ScatterPref(AddonPreferences):
    bl_idname = __name__

    scatter_01_count = IntProperty( 
            name="Emission Number",
            subtype='NONE',
            default=1000,
            min=0,
            )#percentage couvert? 
    scatter_01_seed = IntProperty( 
            name="Emission Seed Value",
            subtype='NONE',
            default=5,
            min=0,
            )
    scatter_01_seed_is_random = BoolProperty( 
            name="Emission Random Seed Value",
            subtype='NONE',
            default=True,
            )
    scatter_01_particle_size = FloatProperty(
            name="Render Scale",
            subtype='NONE',
            default=0.25,
            min=0.01,
            max=3
            )
    scatter_01_size_random = FloatProperty(
            name="Render Scale Randomness",
            subtype='NONE',
            default=0.35,
            min=0,
            max=1
            )
    scatter_01_phase_factor_random = FloatProperty(
            name="Rotation Randomize Phase",
            subtype='NONE',
            default=2,
            min=0,
            max=2
            )
    scatter_01_display_percentage = IntProperty(
            name="Viewport Display Percentage",
            subtype='NONE',
            default=100,
            min=0,
            max=100
            )

    scatter_01_open= bpy.props.BoolProperty(
        name="Show Debug Tools",
        description="Expand me senpai",
        default=False
    )
######################################################################################

    def draw(self, context): #DRAWING HERE - DRAWING HERE - DRAWING HERE- DRAWING HERE
        layout = self.layout
        full= "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
        addon_prefs = context.preferences.addons[__name__].preferences

        box = layout.box()
        col = box.column()
        row = col.row(align=True)
        row.alignment = 'LEFT'
        row.prop(self,
                 'scatter_01_open',
                 text="Custom Scattering Slot 01",
                 emboss=False,
                 icon="PLAY" if self.scatter_01_open else "PLAY")
        if self.scatter_01_open:
            col.prop(self,"scatter_01_count")
            if addon_prefs.scatter_01_seed_is_random == True:
                col.prop(self,"scatter_01_seed_is_random")
            else:
                col.prop(self,"scatter_01_seed_is_random")
                col.prop(self,"scatter_01_seed")
            col.prop(self,"scatter_01_particle_size")
            col.prop(self,"scatter_01_size_random")
            col.prop(self,"scatter_01_phase_factor_random")
            col.prop(self,"scatter_01_display_percentage")


######################################################################################
######################################################################################
# # # # # # # # # # # #        SCATTER OPERATOR                # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_OT_Custom01(bpy.types.Operator):
    bl_idname = "scatter.custom01"
    bl_label = "custom scattering operator 01"
    bl_description = ""

    def execute(self, context):
        context = bpy.context
        addon_prefs = context.preferences.addons[__name__].preferences
        scene = context.scene
        A = context.object
        ### ### Naming
        slotname = "Custom1"
        if len(bpy.context.selected_objects) > 2:
            particlename = bpy.context.selected_objects[1].name + " ..."
        else:
            particlename = bpy.context.selected_objects[1].name #BUG why the f does the name change when multiple execution ???
        pref = "SCATTER: ["+slotname+"] ["+particlename+"] v."
        i = 1
        name = "%s%d" % (pref, i)
        while A.modifiers.get(name):    
            i += 1
            name = "%s%d" % (pref, i)
        ### ### add particle system + rename and add selection in new coll
        m = A.modifiers.new(name, type='PARTICLE_SYSTEM')
        A.select_set(state=False)
        for o in bpy.context.selected_objects:
            o_collection = find_collection(bpy.context, o)
            new_collection = make_collection(name , o_collection)
            new_collection.objects.link(o)
            o_collection.objects.unlink(o)
        A.select_set(state=True)
        ### ### particle parameters
        ps = m.particle_system
        ps.name = name
        ps.settings.name = name
        ps.settings.type = 'HAIR'
        ps.settings.render_type = 'COLLECTION'
        ps.settings.instance_collection = bpy.data.collections[name]
        ps.settings.particle_size = addon_prefs.scatter_01_particle_size
        ps.settings.hair_length = 4
        ps.settings.size_random = addon_prefs.scatter_01_size_random
        ps.settings.count = addon_prefs.scatter_01_count
        if addon_prefs.scatter_01_seed_is_random == True:
            bpy.context.object.particle_systems[name].seed = random.randint(0,10000)
        else:
            bpy.context.object.particle_systems[name].seed = addon_prefs.scatter_01_seed
        ps.settings.display_percentage = addon_prefs.scatter_01_display_percentage
        ps.settings.use_advanced_hair = True
        ps.settings.use_rotations = True
        ps.settings.rotation_factor_random = 1
        ps.settings.phase_factor = 1
        ps.settings.phase_factor_random = addon_prefs.scatter_01_phase_factor_random
        ### ### texture parameters
        bpy.ops.texture.new()
        texturename = name
        bpy.data.textures[-1].name = texturename
        bpy.data.textures[texturename].type = 'CLOUDS' #LATER SUPPORT OTHER NOISE TYPE AND OPTIONS
        bpy.data.textures[texturename].noise_scale = 0.45 #FUTURE PARAMETERS = Scattering noise scale #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        bpy.data.textures[texturename].noise_depth = 0 #FUTURE PARAMETERS = Scattering noise depth #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        bpy.data.textures[texturename].contrast = 3 #FUTURE PARAMETERS = Scattering border (contrast) #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        bpy.data.textures[texturename].intensity = 1 #FUTURE PARAMETERS = Scattering range (brightness) #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        ps.settings.texture_slots.add().texture = bpy.data.textures[texturename]
        ps.settings.texture_slots[0].blend_type = 'MULTIPLY'
        ps.settings.texture_slots[0].use_map_time = False
        ps.settings.texture_slots[0].use_map_density = True
        ps.settings.texture_slots[0].density_factor = 1 #FUTURE PARAMETERS = Scattering density Influence
        ps.settings.texture_slots[0].use_map_length = True 
        ps.settings.texture_slots[0].length_factor = 0.3 #FUTURE PARAMETERS = Scattering size Influence
        ps.settings.texture_slots[0].scale[1] = 1 #FUTURE PARAMETERS = scalex #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        ps.settings.texture_slots[0].scale[2] = 1 #FUTURE PARAMETERS = scaley #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        ps.settings.texture_slots[0].scale[0] = 1 #FUTURE PARAMETERS = scalez #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        ps.settings.texture_slots[0].offset[1] = 0 #FUTURE PARAMETERS = offsetx #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        ps.settings.texture_slots[0].offset[2] = 0 #FUTURE PARAMETERS = offsety #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        ps.settings.texture_slots[0].offset[0] = 0 #FUTURE PARAMETERS = offsetz #LATER GIVE VARIATION RANDOMNESS ON RANGE 
        ps.settings.texture_slots[0].texture_coords = 'GLOBAL'
        return {'FINISHED'}

######################################################################################
######################################################################################
# # # # # # # # # # # #            N MENU                      # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_PT_ScatteringPanel(bpy.types.Panel):
    bl_idname = "Scatter_PT_ScatteringPanel"
    bl_label = "Scatter"
    bl_category = "Scatter"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        bigbutton = layout.column()
        bigbutton.scale_y=2
        bigbutton.operator('scatter.custom01', text="SCATTER",  icon='PLAY')

######################################################################################
######################################################################################
# # # # # # # # # # # #                 REG                    # # # # # # # # # # # # 
######################################################################################
######################################################################################

def register():
    bpy.utils.register_class(ScatterPref)
    bpy.utils.register_class(Scatter_OT_Custom01)
    bpy.utils.register_class(Scatter_PT_ScatteringPanel)

def unregister():
    bpy.utils.unregister_class(ScatterPref)
    bpy.utils.unregister_class(Scatter_OT_Custom01)
    bpy.utils.unregister_class(Scatter_PT_ScatteringPanel)


if __name__ == "__main__":
    register()
    #write ops to do on script exec

Scatter_v01.py (12.3 KB)

Okay the script is now in an addon form.
in the N panel you will find a little operator scattering button.
on the properties panel you will find some customization options for the only custom skattering slot we have yet.

5 Likes

I get this error when clicking on scatter

screenshot%202019-09-02%2000%2031%2045

Very useful!

As it don’t have random texture yet, I tested changing manually and it works fine… Very promising and works like a charm

Trying to get your result in post #1 now :sweat_smile:

1 Like