GLSL Controllable Mist

Features:
- Can change color

  • Can change distance
  • Exponential, Quadratic and Linear models
  • Plane or Sphere mist shape (plane=fast, sphere=accurate)

Demo File:
Controllable Mist v0_1.zip (958 KB)

Note that the demo file also contains documentation. It is a little harder to set up than I would like, but definitely passable.

Notes:
This is probably optimizable, but I’m not a fan of GLSL. If someone finds a problem, I’ll fix it, if they want a feature, I’ll consider it, but as far as I’m concerned, I’d be happy to never touch it again.
Have fun!

Images:


Not the clearest in the world, but the mist color has changed, as has it’s depth. The top image it is both darker, redder and closer. Download the file and have a play

2 Likes

The actual script is here:


import bge


obj = None


MIST_LINEAR = 1.0
MIST_QUADRATIC = 2.0
MIST_EXPONENTIAL = 3.0


MIST_PLANE = 1.0
MIST_SPHERE = 2.0


def setMistColor(rgba):
    '''Sets mist color
    Inputs:
     - rgb = any iterable with the first three components being r, g, b
    '''
    update_camera_info()
    obj['R'] = rgba[0]
    obj['G'] = rgba[1]
    obj['B'] = rgba[2]
    
    if len(rgba) == 4:
        bge.render.setBackgroundColor(rgba)
    else:
        bge.render.setBackgroundColor([rgba[0], rgba[1], rgba[2], 1])
    
def getMistColor():
    '''Returns the mists current color'''
    return [obj['R'], obj['G'], obj['B']]
    
def setMistParameters(density, dist=0, model=MIST_EXPONENTIAL, clipping=MIST_SPHERE):
    '''Sets the mist parameters:
    Inputs: 
     - density = The thickness of the mist. Note that for linear and quadratic, this is the 'depth'
     - dist (optional) = The start distance of the mist
     - model (optional) = The type of mist. Options are MIST_LINEAR, MIST_QUADRATIC, MIST_EXPONENTIAL
     - clipping (optional) = The clipping method of the mist. Can either be a fast-to-compute plane, or a slower but more accurate sphere
    '''
    update_camera_info()
    obj['model'] = model
    obj['dist'] = dist
    obj['density'] = density
    obj['clipping'] = clipping
    
def getMistParameters():
    '''Returns the current mist parameters:
    Outputs:
     - dist = The start distance of the mist
     - density = The thickness of the mist. Note that for linear and quadratic, this is the 'depth'
     - model = The type of mist. Options are MIST_LINEAR, MIST_QUADRATIC, MIST_EXPONENTIAL
     - clipping = The clipping method of the mist. Can either be a fast-to-compute plane, or a slower but more accurate sphere
    '''
    return obj['density'], obj['dist'], obj['model'], obj['clipping']
    
def enableMist():
    '''Enables the mist'''
    update_camera_info()
    obj['enable'] = 1.0
    
def disableMist():
    '''Disables the mist.
    
    Note: The shader still exists and is running, but is simply piping
    the rendered texture straight through with no alterations'''
    obj['enable'] = 0.0


def update_camera_info():
    '''The mist filter relies on the view limits of the camera. Running
    this function updates the view limits in the script using the 
    scenes active camera
    
    Note: All functions that edit the mist will run this function'''
    cam = bge.logic.getCurrentScene().active_camera
    obj['camNear'] = cam.near #Camera limits are needed for linearization
    obj['camFar'] = cam.far
    obj['fov'] = cam.fov / 360 * 3.14 #FOV is needed for the mist sphere
    obj['aspect'] = bge.render.getWindowWidth()/bge.render.getWindowHeight()




def start(cont):
    '''Starts the mist service. Note that mist is disabled and must
    be enabled with bge.render.enableMist()'''
    # Add the object running the mist service to the module so that 
    # other functions can alter it's properties
    global obj
    obj = cont.owner
    
    # Start the shader
    custom_filter = cont.actuators[0]
    custom_filter.shaderText = shader_text
    cont.activate(custom_filter)




shader_text = '''
uniform sampler2D bgl_RenderedTexture;
uniform sampler2D bgl_DepthTexture;


uniform float dist;
uniform float density;


uniform float camNear; //camera clipping start
uniform float camFar; //camera clipping end
uniform float fov; //camera field of view
uniform float aspect; //camera aspect ratio


uniform float R;
uniform float G;
uniform float B;


uniform float model;
uniform float enable;
uniform float clipping;


float linearize(float depth)
{
    /**Some magic I pinched off Martinish's DOF shader
    I'm not entirely sure how it works, but without it, the mist is
    strange **/
    return -camFar * camNear / (depth * (camFar - camNear) - camFar);
}




float applyMist(float depth)
{
    /** Takes the depth and applies one of the mist models to it, either
    mixing it linearly, quadratically or exponential **/
    float factor = 0.0;
    if (model == 1.0){
        //Linear Mist
        factor = (depth-dist)/(density);
    } else if (model == 2.0){
        //Quadratic Mist
        factor = max(depth - dist, 0.0);
        factor = (factor * factor)/(density);
    } else if (model == 3.0){
        //Exponential Mist
        factor = 1.0 - exp(-(depth - dist)*density);
    }
    return factor;
}


float getDepth(float d_buffer)
{
    float depth = 1.0;
    depth = linearize(d_buffer);
    
    if (depth > camFar){
        //Clip the mist to allow for skyboxes
        depth = 0.0;
    }
    
    if (clipping < 1.5){
        //Mist plane
        depth = depth;
    } else {
        //Mist sphere
        float width = depth * tan(fov);
        float height = width / aspect;
        float y = (gl_TexCoord[0].st.y - 0.5) * 2.0 * height;
        float x = (gl_TexCoord[0].st.x - 0.5) * 2.0 * width;
        
        depth = length(vec3(x,y,depth));//pow(depth, 0.3333);
    }
    
    
        
    return depth;
}


void main() 
{
    vec4 dif = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);
    if (enable > 0.5){
        vec4 mist = vec4(R,G,B,0.0);
        
        float d_buffer = texture2D(bgl_DepthTexture,gl_TexCoord[0].xy).z;
        float depth = getDepth(d_buffer);
        float factor = applyMist(depth);
        
        gl_FragColor = mix(dif, mist, clamp(factor, 0.0, 1.0));
    } else {
        gl_FragColor = dif;
    }
}
'''

And the documentation is attached:

Attachments

Documentation v1.0.pdf (204 KB)

Thanks, this is just what I needed for my project! Alpha release, here I come!

this is a godsend for Draw-distance…

Beautiful! And very nice technique for the glowing sun. Could you explain it in a mini tutorial? Thanks! Good job!

Nice Mist! On my ATi works great :smiley: Finally someone invent professional shader :wink:

I have some problem with run martinsh SSAO and your Mist in the same time. Scripts interrupt between them.
Any idea to make it work?

/*
SSAO GLSL shader v1.2
assembled by Martins Upitis (martinsh) (devlog-martinsh.blogspot.com)
original technique is made by Arkano22 (www.gamedev.net/topic/550699-ssao-no-halo-artifacts/)


changelog:
1.2 - added fog calculation to mask AO. Minor fixes.
1.1 - added spiral sampling method from here:
(http://www.cgafaq.info/wiki/Evenly_distributed_points_on_sphere)
*/
uniform sampler2D bgl_DepthTexture;
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;


#define PI    3.14159265


float width = bgl_RenderedTextureWidth; //texture width
float height = bgl_RenderedTextureHeight; //texture height


vec2 texCoord = gl_TexCoord[0].st;


//------------------------------------------
//general stuff


//make sure that these two values are the same for your camera, otherwise distances will be wrong.


float znear = 0.001; //Z-near
float zfar = 4.0; //Z-far


//user variables
int samples = 6; //ao sample count


float radius = 10.0; //ao radius
float aoclamp = 2; //depth clamp - reduces haloing at screen edges
bool noise = true; //use noise instead of pattern for sample dithering
float noiseamount = 0.0002; //dithering amount


float diffarea = 0.4; //self-shadowing reduction
float gdisplace = 0.4; //gauss bell center
float aowidth = 8.0; //gauss bell width


bool mist = true; //use mist?
float miststart = 0.0; //mist start
float mistend = 1.0; //mist end


bool onlyAO = false; //use only ambient occlusion pass?
float lumInfluence = 0.6; //how much luminance affects occlusion


//--------------------------------------------------------


vec2 rand(vec2 coord) //generating noise/pattern texture for dithering
{
    float noiseX = ((fract(1.0-coord.s*(width/2.0))*0.25)+(fract(coord.t*(height/2.0))*0.75))*2.0-1.0;
    float noiseY = ((fract(1.0-coord.s*(width/2.0))*0.75)+(fract(coord.t*(height/2.0))*0.25))*2.0-1.0;
    
    if (noise)
    {
        noiseX = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453),0.0,1.0)*2.0-1.0;
        noiseY = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453),0.0,1.0)*2.0-1.0;
    }
    return vec2(noiseX,noiseY)*noiseamount;
}


float doMist()
{
    float zdepth = texture2D(bgl_DepthTexture,texCoord.xy).x;
    float depth = -zfar * znear / (zdepth * (zfar - znear) - zfar);
    return clamp((depth-miststart)/mistend,0.0,1.0);
}


float readDepth(in vec2 coord) 
{
    coord.x = clamp(coord.x,0.001,0.999);
    coord.y = clamp(coord.y,0.001,0.999);
    return (2.0 * znear) / (zfar + znear - texture2D(bgl_DepthTexture, coord ).x * (zfar-znear));
}


float compareDepths(in float depth1, in float depth2,inout int far)
{   
    float garea = aowidth; //gauss bell width    
    float diff = (depth1 - depth2)*50.0; //depth difference (0-100)
    //reduce left bell width to avoid self-shadowing
    if (diff<gdisplace)
    {
    garea = diffarea;
    }else{
    far = 1;
    }
    
    float gauss = pow(2.7182,-2.0*(diff-gdisplace)*(diff-gdisplace)/(garea*garea));
    return gauss;
}   


float calAO(float depth,float dw, float dh)
{   
    //float dd = (1.0-depth)*radius;
    float dd = radius;
    float temp = 0.0;
    float temp2 = 0.0;
    float coordw = gl_TexCoord[0].x + dw*dd;
    float coordh = gl_TexCoord[0].y + dh*dd;
    float coordw2 = gl_TexCoord[0].x - dw*dd;
    float coordh2 = gl_TexCoord[0].y - dh*dd;
    
    vec2 coord = vec2(coordw , coordh);
    vec2 coord2 = vec2(coordw2, coordh2);
    
    int far = 0;
    temp = compareDepths(depth, readDepth(coord),far);
    //DEPTH EXTRAPOLATION:
    if (far > 0)
    {
        temp2 = compareDepths(readDepth(coord2),depth,far);
        temp += (1.0-temp)*temp2;
    }
    
    return temp;
} 


void main(void)
{
    vec2 noise = rand(texCoord); 
    float depth = readDepth(texCoord);
    
    float w = (1.0 / width)/clamp(depth,aoclamp,1.0)+(noise.x*(1.0-noise.x));
    float h = (1.0 / height)/clamp(depth,aoclamp,1.0)+(noise.y*(1.0-noise.y));
    
    float pw;
    float ph;
    
    float ao;
    
    float dl = PI*(3.0-sqrt(5.0));
    float dz = 1.0/float(samples);
    float l = 0.0;
    float z = 1.0 - dz/2.0;
    
    for (int i = 0; i <= samples; i ++)
    {     
        float r = sqrt(1.0-z);
        
        pw = cos(l)*r;
        ph = sin(l)*r;
        ao += calAO(depth,pw*w,ph*h);        
        z = z - dz;
        l = l + dl;
    }
    
    ao /= float(samples);
    ao = 1.0-ao;    
    
    if (mist)
    {
    ao = mix(ao, 1.0,doMist());
    }
    
    vec3 color = texture2D(bgl_RenderedTexture,texCoord).rgb;
    
    vec3 lumcoeff = vec3(0.299,0.587,0.114);
    float lum = dot(color.rgb, lumcoeff);
    vec3 luminance = vec3(lum, lum, lum);
    
    vec3 final = vec3(color*mix(vec3(ao),vec3(1.0),luminance*lumInfluence));//mix(color*ao, white, luminance)
    
    if (onlyAO)
    {
    final = vec3(mix(vec3(ao),vec3(1.0),luminance*lumInfluence)); //ambient occlusion only
    }
    
    
    gl_FragColor = vec4(final,1.0); 
    
}

Try putting the shaders on different passes (the number on the logic brick). I can’t remember if lower is highest priority or the other way around, but try make the mist draw on top of the ssao.

It’s a great resource. Thanks for your hard work!

Thanks, now it works ( SSAO must be define before on lower passes)

How about stars and a moon.

Aloha Geoff, I am working on integrating this into my project and I have come to a point where I need a little help adjusting change_mist.py to suit my needs. My setup is has 2 scenes, a main scene and a background scene. I am adjusting the mist in the background scene to create a day/night cycle. I want to adjust the mist according to my players local zenith, i.e. the sunrise and sunset should occur whenever the sun object is 85-95 degrees from the players local z+ axis. I figure I need to get the angle between the sun’s z+ and the players z+ and change the mist when it is between 85-95 degrees. I am toying with the settings of change_mist.py to see if I can figure it out, but I could use a tip or two if anyone knows how to do this.


#change_mist.py
import mist
import bge
import mathutils


cont = bge.logic.getCurrentController()
own = cont.owner


azimuth = own.getAxisVect([0,0,1]).dot([0,0,1]) #can anyone explain this line?


density = max(0.03 - azimuth*0.03, 0.0)
R = 0.3 * (azimuth + 0.5)
G = 0.4 * (azimuth + 0.4)
B = 1.0 * (azimuth + 0.3)
color = [R,G,B]


mist.setMistColor(color)
mist.setMistParameters(density, 0, mist.MIST_EXPONENTIAL, mist.MIST_SPHERE)


if 'enabled' not in own:
    mist.enableMist()
    own['enabled'] = 1

Attachments

mist change test.blend (4.43 MB)

In 2.75 you can do this:


import bge

scene = bge.logic.getCurrentScene()
world = scene.world

world.mistColor = [0.0, 1.0, 0.5]]

Thanks, my issue is in trying to activate the change at the right time. My math brain just isn’t working right now. Also I opened this question in the discussions forum to keep this thread focused on the resource.

Here is a slightly modifed version of the “mist.py” for usage in UPBGE 0.3 Eevee:

import bge
import bpy

obj = None

MIST_LINEAR = 1.0
MIST_QUADRATIC = 2.0
MIST_EXPONENTIAL = 3.0

MIST_PLANE = 1.0
MIST_SPHERE = 2.0

def setMistColor(rgba):
    '''Sets mist color
    Inputs:
     - rgb = any iterable with the first three components being r, g, b
    '''
    update_camera_info()
    obj['R'] = rgba[0]
    obj['G'] = rgba[1]
    obj['B'] = rgba[2]
    
    # if len(rgba) == 4:
        # bge.render.setBackgroundColor(rgba)
    # else:
        # bge.render.setBackgroundColor([rgba[0], rgba[1], rgba[2], 1])
    bpy.data.worlds["World"].color=rgba
    
def getMistColor():
    '''Returns the mists current color'''
    return [obj['R'], obj['G'], obj['B']]
    
def setMistParameters(density, dist=0, model=MIST_EXPONENTIAL, clipping=MIST_SPHERE):
    '''Sets the mist parameters:
    Inputs: 
     - density = The thickness of the mist. Note that for linear and quadratic, this is the 'depth'
     - dist (optional) = The start distance of the mist
     - model (optional) = The type of mist. Options are MIST_LINEAR, MIST_QUADRATIC, MIST_EXPONENTIAL
     - clipping (optional) = The clipping method of the mist. Can either be a fast-to-compute plane, or a slower but more accurate sphere
    '''
    update_camera_info()
    obj['model'] = model
    obj['dist'] = dist
    obj['density'] = density
    obj['clipping'] = clipping
    
def getMistParameters():
    '''Returns the current mist parameters:
    Outputs:
     - dist = The start distance of the mist
     - density = The thickness of the mist. Note that for linear and quadratic, this is the 'depth'
     - model = The type of mist. Options are MIST_LINEAR, MIST_QUADRATIC, MIST_EXPONENTIAL
     - clipping = The clipping method of the mist. Can either be a fast-to-compute plane, or a slower but more accurate sphere
    '''
    return obj['density'], obj['dist'], obj['model'], obj['clipping']
    
def enableMist():
    '''Enables the mist'''
    update_camera_info()
    obj['enable'] = 1.0
    
def disableMist():
    '''Disables the mist.
    
    Note: The shader still exists and is running, but is simply piping
    the rendered texture straight through with no alterations'''
    obj['enable'] = 0.0

def update_camera_info():
    '''The mist filter relies on the view limits of the camera. Running
    this function updates the view limits in the script using the 
    scenes active camera
    
    Note: All functions that edit the mist will run this function'''
    cam = bge.logic.getCurrentScene().active_camera
    obj['camNear'] = cam.near #Camera limits are needed for linearization
    obj['camFar'] = cam.far
    obj['fov'] = cam.fov / 360 * 3.14 #FOV is needed for the mist sphere
    obj['aspect'] = bge.render.getWindowWidth()/bge.render.getWindowHeight()


def start(cont):
    '''Starts the mist service. Note that mist is disabled and must
    be enabled with bge.render.enableMist()'''
    # Add the object running the mist service to the module so that 
    # other functions can alter it's properties
    global obj
    obj = cont.owner
    
    # Start the shader
    custom_filter = cont.actuators[0]
    custom_filter.shaderText = shader_text
    cont.activate(custom_filter)


shader_text = '''
in vec4 bgl_TexCoord;
#define gl_TexCoord glTexCoord
vec4 glTexCoord[4] = vec4[](bgl_TexCoord,bgl_TexCoord,bgl_TexCoord,bgl_TexCoord);
out vec4 FragColor;


uniform sampler2D bgl_RenderedTexture;
uniform sampler2D bgl_DepthTexture;

uniform float dist;
uniform float density;

uniform float camNear; //camera clipping start
uniform float camFar; //camera clipping end
uniform float fov; //camera field of view
uniform float aspect; //camera aspect ratio

uniform float R;
uniform float G;
uniform float B;

uniform float model;
uniform float enable;
uniform float clipping;




float linearize(float depth)
{
    /**Some magic I pinched off Martinish's DOF shader
    I'm not entirely sure how it works, but without it, the mist is
    strange **/
	return -camFar * camNear / (depth * (camFar - camNear) - camFar);
}


float applyMist(float depth)
{
    /** Takes the depth and applies one of the mist models to it, either
    mixing it linearly, quadratically or exponential **/
    float factor = 0.0;
    if (model == 1.0){
        //Linear Mist
        factor = (depth-dist)/(density);
    } else if (model == 2.0){
        //Quadratic Mist
        factor = max(depth - dist, 0.0);
        factor = (factor * factor)/(density);
    } else if (model == 3.0){
        //Exponential Mist
        factor = 1.0 - exp(-(depth - dist)*density);
		//Exponential Squared Mist
		//factor = 1.0 - exp2(-sqrt((depth - dist)*density));
    }
    return factor;
}

float getDepth(float d_buffer)
{
    float depth = 1.0;
    depth = linearize(d_buffer);
    
    if (depth > camFar){
        //Clip the mist to allow for skyboxes
        depth = 0.0;
    }
    
    if (clipping < 1.5){
        //Mist plane
        depth = depth;
    } else {
        //Mist sphere
        float width = depth * tan(fov);
        float height = width / aspect;
        float y = (gl_TexCoord[0].st.y - 0.5) * 2.0 * height;
        float x = (gl_TexCoord[0].st.x - 0.5) * 2.0 * width;
        
        depth = length(vec3(x,y,depth));//pow(depth, 0.3333);
		//depth = pow(depth, 0.3333);
    }
    
    
        
    return depth;
}

void main() 
{
    vec4 dif = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);
    if (enable > 0.5){
        vec4 mist = vec4(R,G,B,0.0);
        
        float d_buffer = texture2D(bgl_DepthTexture,gl_TexCoord[0].xy).x;
        float depth = getDepth(d_buffer);
        float factor = applyMist(depth);
        
        gl_FragColor = mix(dif, mist, clamp(factor, 0.0, 1.0));
    } else {
        gl_FragColor = dif;
    }
}
'''
1 Like