Are there any shader model v1.3 (or lower) "bloom" or "glowing line" shaders?

Started by
6 comments, last by OrangyTang 16 years, 11 months ago
I'm trying to create a very simple 2D game using simple vector graphics but I'd like to have a "glowing line" effect like Defcon or Project VEX has. After doing some research it looks like the easiest way to do this is to apply a "bloom" shader, but all of the bloom shaders I've found seem to require shader model v2.0. Well, I'm trying to create the game so it has very low system requirements so I'd like to only require shader model v1.3 or lower. Right now I'm using XNA (which requires shader model v1.1 or higher) but I'd definitely willing to switch to OpenGL if this effect is easier to create with that. (eg. Project VEX says it uses a SDL and OpenGL with "full-screen bloom effect" and the game runs on my low-end laptop so I know this effect is possible on low-end hardware!) Thanks...
Advertisement
Well those things generally work by rendering to a texture, making the things you want to glow brighter somehow (for lines, you could render into the alpha channel just the lines as 255, and everything else as 0 and then multiply by alpha) and rendering that to another texture, then blending the top on top of each other blurring the one with the brightened bits. Now for the shaders you generally have the blur part work by sampling several parts of the texture, it takes one texture coordinate offsets it a few times and samples in several different places. This is a dependent texture read which I think you need SM v2 or above for. However you could pass several sets of texture coordinates with the offsets already applied so you don't need any dependent texture reads. You could also do it without shaders by cunning use of blending (basically get the thing you want to blur render it additively, then offset the texture slightly, render additively again etc).
Quote:Original post by Monder
Now for the shaders you generally have the blur part work by sampling several parts of the texture, it takes one texture coordinate offsets it a few times and samples in several different places. This is a dependent texture read which I think you need SM v2 or above for.

Thats not a dependant texture read - dependant texture reads are where the result from one texture is used to adjust the texture coords used to sample another (ie. one is dependant on the other). This is usually how distortion/heat haze/etc. effects are done.

sofakng: Your basic glow needs something like this:

1. Draw 'glowing' bits to texture (or draw it to the backbuffer, then copy it to a texture).
2. Blur this texture horizontally. The simplest non-shader way to do this is to repeatedly draw it to another texture, each time with a small horizontal offset. Each time you draw it with additive blending and with a certain amount of transparency (this is the weight value).
3. Take the result of the horizontal blur, and blur this vertically (again, to another texture).
4. Draw the final blur result over the final scene, with additive blending.

The trick to good blur is getting the weights right. If you're doing 10 samples/drawings on the horizonal pass then the simplest way is to have weights of 1/10. However this tends to produce a rather box-y blur. Ideally you calculate the weights using a normal distribution curve or a sine curve so that samples near the center have more influence than samples near the edge.
Quote:dependant texture reads are where the result from one texture is used to adjust the texture coords used to sample another (ie. one is dependant on the other)


Whoops, I really should know that. You forget these things when you haven't done anything with shaders for a couple of years [embarrass]
How about this example. uses cg.
http://http.download.nvidia.com/developer/SDK/Individual_Samples/samples.html#cg_VolumeLine

Although I don't think defcon uses shaders, since it works on a Geforce 2(I think?)
This might help:
Real-Time Glow (gamasutra.com - probably requires a login, but registration is free, and very worthwhile)

John B
The best thing about the internet is the way people with no experience or qualifications can pretend to be completely superior to other people who have no experience or qualifications.
Thanks for the replies everybody!

I still have a few questions about this... :(

Quote:Original post by OrangyTang
sofakng: Your basic glow needs something like this:

1. Draw 'glowing' bits to texture (or draw it to the backbuffer, then copy it to a texture).

You mean I just draw my simple line or my polygon like normal but draw it to a texture?

Quote:
2. Blur this texture horizontally. The simplest non-shader way to do this is to repeatedly draw it to another texture, each time with a small horizontal offset. Each time you draw it with additive blending and with a certain amount of transparency (this is the weight value).

I'm a little confused on this part... are you saying I just draw another line (or polygon?) on top of the existing polygon but I draw it a little shorter?

Quote:
3. Take the result of the horizontal blur, and blur this vertically (again, to another texture).

I'm also a little confused on this part (sorry!!)... what if my line is only 2 pixels thick? Do I just draw vertical strips slightly smaller than the height of my line? (but I loop through the entire width of the line?)

Quote:
4. Draw the final blur result over the final scene, with additive blending.

Ok, that's an easy step... :)

Quote:The trick to good blur is getting the weights right. If you're doing 10 samples/drawings on the horizonal pass then the simplest way is to have weights of 1/10. However this tends to produce a rather box-y blur. Ideally you calculate the weights using a normal distribution curve or a sine curve so that samples near the center have more influence than samples near the edge.

When you say "weight" do you transparency value? (...and you also saying that the weight of my main line will be lowest [eg. the brightest] and the further out it will be darker, right?)
Quote:Original post by sofakng
Quote:Original post by OrangyTang
sofakng: Your basic glow needs something like this:

1. Draw 'glowing' bits to texture (or draw it to the backbuffer, then copy it to a texture).

You mean I just draw my simple line or my polygon like normal but draw it to a texture?

Yes.

Quote:
Quote:
2. Blur this texture horizontally. The simplest non-shader way to do this is to repeatedly draw it to another texture, each time with a small horizontal offset. Each time you draw it with additive blending and with a certain amount of transparency (this is the weight value).

I'm a little confused on this part... are you saying I just draw another line (or polygon?) on top of the existing polygon but I draw it a little shorter?

You draw a quad containing the blur texture (created in 1) to another texture. After the first step you work entirely with the created texture, the lines aren't needed any more because they're captured in the input texture.

Heres some (old) code of mine to do this. It's not the best code but it does the job and should illustrate the general method:
package quix.effect.glow;import java.nio.ByteBuffer;import java.nio.IntBuffer;import org.lwjgl.BufferUtils;import org.lwjgl.opengl.Display;import org.lwjgl.opengl.GL11;import org.lwjgl.opengl.GL12;import org.lwjgl.opengl.GL13;import org.lwjgl.opengl.GLContext;import com.shavenpuppy.jglib.opengl.GL;/** * @author John Campbell */public class MultiSampleGlowFilter implements GlowFilter{	/** Actual width and height of the texture */	private final int TEXTURE_WIDTH;	private final int TEXTURE_HEIGHT;		/** The amount of the texture actually used for the blur filter. Must be less or equal to the actual size. */	private final int BLUR_WIDTH;	private final int BLUR_HEIGHT;		private final int samples;	private float[] sampleWeights;	private float[] sampleOffsets;		private boolean useMultiTexture;	private final boolean isFullSizedTexture;		private final float texelSizeX;	private final float texelSizeY;		private int glowTexture;	private int prevGlowTexture;		private float persistance;		public MultiSampleGlowFilter(int numSamples)	{		System.out.println("Creating MultiSample glow filter.");		System.out.println("\tNumber of samples: "+numSamples);				IntBuffer iBuff = BufferUtils.createIntBuffer(16);		GL11.glGetInteger(GL13.GL_MAX_TEXTURE_UNITS, iBuff);		int avalibleTextureUnits = iBuff.get();				// Can we use the multi-texturing varient?		useMultiTexture = (GLContext.getCapabilities().OpenGL13						// For multi-texturing							&& avalibleTextureUnits >= 2			// Need at least two texture units							&& GLContext.getCapabilities().GL_ARB_texture_env_add);	// ADD environment for combining samples				if (useMultiTexture)			System.out.println("\tUsing multitexturing path for glow filter.");		else			System.out.println("\tUsing single texture path for glow filter.");				TEXTURE_WIDTH = 512;		TEXTURE_HEIGHT = 512;		BLUR_WIDTH = 400;		BLUR_HEIGHT = 300;				samples = numSamples;		isFullSizedTexture = false;				// NB: Probably a good idea to keep below 0.83. Anything above that will leave streaking effects on screen due to rounding etc.		persistance = GlowFilter.NORMAL_PERSISTANCE;				sampleOffsets = new float[samples];		for (int i=0; i<sampleOffsets.length; i++)			sampleOffsets = i;				// Generate weights for each sample		sampleWeights = new float[samples];		for (int i=0; i<sampleWeights.length; i++)		{			// Samples are based on a shifted cos curve			float offset = Math.abs( sampleOffsets );			sampleWeights = (float)(Math.cos( (offset/samples) * Math.PI) + 1f) *0.5f;		}				// Normalise all weights to sum to 1, then double to get a nice bright glow.		float norm = 0;		for (int i=0; i<sampleWeights.length; i++)			norm += sampleWeights;		for (int i=0; i<sampleWeights.length; i++)		{			sampleWeights /= norm;			sampleWeights *= 1.0f;		}				texelSizeX = 1f / TEXTURE_WIDTH;		texelSizeY = 1f / TEXTURE_HEIGHT;				// Generate tex names		glowTexture = createBlankTexture();		prevGlowTexture = createBlankTexture();	}	private int createBlankTexture()	{		GL.scratch.ints.clear().limit(1);		GL11.glGenTextures(GL.scratch.ints);		int texId = GL.scratch.ints.get(0);				// Temp buffer for creating blank texture		ByteBuffer imgBuffer = BufferUtils.createByteBuffer(3 * TEXTURE_WIDTH * TEXTURE_HEIGHT);				// Create new texture		GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);		GL11.glTexImage2D(	GL11.GL_TEXTURE_2D,							0,							GL11.GL_RGB,							TEXTURE_WIDTH,							TEXTURE_HEIGHT,							0,							GL11.GL_RGB,							GL11.GL_UNSIGNED_BYTE,							imgBuffer);		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);				GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);				return texId;	}		public void destroy()	{		// Clean up texture usage		GL.scratch.ints.clear();		GL.scratch.ints.put(glowTexture);				GL11.glDeleteTextures(GL.scratch.ints);	}		public boolean isFullSizedTexture()	{		return isFullSizedTexture;	}		public void setPersistance(float persistance)	{		this.persistance = persistance;	}		private void captureScreen()	{		bindTexture();				// Copy rendered scene to the texture		GL11.glCopyTexSubImage2D(	GL11.GL_TEXTURE_2D, 0,									0, 0,									0, 0,									BLUR_WIDTH, BLUR_HEIGHT	);		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);				blurTexture();	}		private void blurTexture()	{		preRender();				GL11.glEnable(GL11.GL_BLEND);		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);				// Horizontal blur		if (useMultiTexture)		{			GL13.glActiveTexture(GL13.GL_TEXTURE0);			GL11.glEnable(GL11.GL_TEXTURE_2D);			GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);						GL13.glActiveTexture(GL13.GL_TEXTURE1);			GL11.glEnable(GL11.GL_TEXTURE_2D);			GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);						GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_ADD);						for (int i=0; i<samples; i++)			{				float off = sampleOffsets;				blurSamples(off, off/2f, sampleWeights);			}						GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);		}		else		{			GL11.glEnable(GL11.GL_TEXTURE_2D);			GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);						blur(1f, 0.5f);			blur(-1f, -0.5f);		}							bindTexture();		GL11.glCopyTexSubImage2D(	GL11.GL_TEXTURE_2D, 0,									0, 0,									0, 0,									BLUR_WIDTH, BLUR_HEIGHT	);			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);				// Vertical blur		if (useMultiTexture)		{			GL13.glActiveTexture(GL13.GL_TEXTURE0);			GL11.glEnable(GL11.GL_TEXTURE_2D);			GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);						GL13.glActiveTexture(GL13.GL_TEXTURE1);			GL11.glEnable(GL11.GL_TEXTURE_2D);			GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);						GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_ADD);						for (int i=0; i<samples; i++)			{				float off = sampleOffsets;				blurSamples(-off/2f, off, sampleWeights);			}						GL13.glActiveTexture(GL13.GL_TEXTURE1);			GL11.glDisable(GL11.GL_TEXTURE_2D);						GL13.glActiveTexture(GL13.GL_TEXTURE0);						GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);		}		else		{			blur(-0.5f,  1f);			blur(0.5f, -1f);		}				addPersistance();					GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);		GL11.glCopyTexSubImage2D(	GL11.GL_TEXTURE_2D, 0,									0, 0,									0, 0,									BLUR_WIDTH, BLUR_HEIGHT	);				GL11.glDisable(GL11.GL_TEXTURE_2D);		GL11.glDisable(GL11.GL_BLEND);				postRender();	}		private void blur(float xOffset, float yOffset)	{		float x=0;		float y=0;				for (int i=-0; i<samples; i++)		{			GL11.glBegin(GL11.GL_QUADS);			{				GL11.glColor4f(1f, 1f, 1f, sampleWeights);								float uMax = (1f / TEXTURE_WIDTH) * BLUR_WIDTH;				float vMax = (1f / TEXTURE_HEIGHT) * BLUR_HEIGHT;								GL11.glTexCoord2f(0f, 0f);				GL11.glVertex2f(x, y);								GL11.glTexCoord2f(uMax, 0f);				GL11.glVertex2f(x+BLUR_WIDTH, y);								GL11.glTexCoord2f(uMax, vMax);				GL11.glVertex2f(x+BLUR_WIDTH, y+BLUR_HEIGHT);								GL11.glTexCoord2f(0f, vMax);				GL11.glVertex2f(x, y+BLUR_HEIGHT);			}			GL11.glEnd();						x += xOffset;			y += yOffset;		}	}		private void blurSamples(float xOffset, float yOffset, float weight)	{		float uMax = (1f / TEXTURE_WIDTH) * BLUR_WIDTH;		float vMax = (1f / TEXTURE_HEIGHT) * BLUR_HEIGHT;				GL11.glBegin(GL11.GL_QUADS);		{			GL11.glColor4f(1f, 1f, 1f, weight);						GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE0,									-xOffset*texelSizeX,									-yOffset*texelSizeY		);			GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE1,									xOffset*texelSizeX,									yOffset*texelSizeY		);			GL11.glVertex2f(0f, 0f);									GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE0,									-xOffset*texelSizeX + uMax,									-yOffset*texelSizeY		);			GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE1,									uMax + xOffset*texelSizeX,									yOffset*texelSizeY		);			GL11.glVertex2f(BLUR_WIDTH, 0f);									GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE0,									-xOffset*texelSizeX + uMax,									-yOffset*texelSizeY + vMax		);			GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE1,									xOffset*texelSizeX + uMax,									yOffset*texelSizeY + vMax		);			GL11.glVertex2f(BLUR_WIDTH, BLUR_HEIGHT);									GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE0,										-xOffset*texelSizeX,									-yOffset*texelSizeY + vMax		);			GL13.glMultiTexCoord2f(	GL13.GL_TEXTURE1,										xOffset*texelSizeX,									yOffset*texelSizeY + vMax		);			GL11.glVertex2f(0f, BLUR_HEIGHT);					}		GL11.glEnd();	}	/*	private void blurRadial()	{		float uScale = 0f;		float vScale = 0f;						int numLayers = 40;		for (int i=-0; i<numLayers; i++)		{			GL11.glBegin(GL11.GL_QUADS);			{				float alpha = (float)(numLayers-i) / (float)numLayers;				GL11.glColor4f(1f, 1f, 1f, alpha);								float uMax = (1f / TEXTURE_WIDTH) * BLUR_WIDTH;				float vMax = (1f / TEXTURE_HEIGHT) * BLUR_HEIGHT;								GL11.glTexCoord2f(uScale, vScale);				GL11.glVertex2f(0f, 0f);								GL11.glTexCoord2f(uMax-uScale, vScale);				GL11.glVertex2f(BLUR_WIDTH, 0f);								GL11.glTexCoord2f(uMax-uScale, vMax-vScale);				GL11.glVertex2f(BLUR_WIDTH, BLUR_HEIGHT);								GL11.glTexCoord2f(uScale, vMax-vScale);				GL11.glVertex2f(0f, BLUR_HEIGHT);			}			GL11.glEnd();						uScale += 0.002f;			vScale += 0.002f;		}	}*/		private void addPersistance()	{		if (useMultiTexture)			GL13.glActiveTexture(GL13.GL_TEXTURE0);				GL11.glEnable(GL11.GL_TEXTURE_2D);		GL11.glBindTexture(GL11.GL_TEXTURE_2D, prevGlowTexture);				GL11.glEnable(GL11.GL_BLEND);		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);				GL11.glBegin(GL11.GL_QUADS);		{			GL11.glColor4f(1f, 1f, 1f, persistance);						float uMax = (1f / TEXTURE_WIDTH) * BLUR_WIDTH;			float vMax = (1f / TEXTURE_HEIGHT) * BLUR_HEIGHT;						GL11.glTexCoord2f(0f, 0f);			GL11.glVertex2f(0f, 0f);						GL11.glTexCoord2f(uMax, 0f);			GL11.glVertex2f(BLUR_WIDTH, 0f);						GL11.glTexCoord2f(uMax, vMax);			GL11.glVertex2f(BLUR_WIDTH, BLUR_HEIGHT);						GL11.glTexCoord2f(0f, vMax);			GL11.glVertex2f(0f, BLUR_HEIGHT);		}		GL11.glEnd();	}		public void bindTexture()	{		GL11.glEnable(GL11.GL_TEXTURE_2D);		GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);	}		public void preScene()	{		GL11.glViewport(0, 0, BLUR_WIDTH, BLUR_HEIGHT);				// Also swap our current and previous textures		int temp = prevGlowTexture;		prevGlowTexture = glowTexture;		glowTexture = temp;	}		public void postScene()	{		captureScreen();				GL11.glViewport(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight());		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);				if (useMultiTexture)		{			GL13.glActiveTexture(GL13.GL_TEXTURE1);			GL11.glDisable(GL11.GL_TEXTURE_2D);						GL13.glActiveTexture(GL13.GL_TEXTURE0);			GL11.glDisable(GL11.GL_TEXTURE_2D);		}		else			GL11.glDisable(GL11.GL_TEXTURE_2D);	}		/** Render the glow filter on top of the existing scene. */	public void render()	{		preRender();		{			GL11.glEnable(GL11.GL_TEXTURE_2D);			GL11.glBindTexture(GL11.GL_TEXTURE_2D, glowTexture);						GL11.glEnable(GL11.GL_BLEND);			GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);						GL11.glBegin(GL11.GL_QUADS);			{				GL11.glColor4f(1f, 1f, 1f, 0.1f);								float uMax = (1f / TEXTURE_WIDTH) * BLUR_WIDTH;				float vMax = (1f / TEXTURE_HEIGHT) * BLUR_HEIGHT;								GL11.glTexCoord2f(0f, 0f);				GL11.glVertex2f(0f, 0f);								GL11.glTexCoord2f(uMax, 0f);				GL11.glVertex2f(BLUR_WIDTH, 0f);								GL11.glTexCoord2f(uMax, vMax);				GL11.glVertex2f(BLUR_WIDTH, BLUR_HEIGHT);								GL11.glTexCoord2f(0f, vMax);				GL11.glVertex2f(0f, BLUR_HEIGHT);			}			GL11.glEnd();													/*	GL11.glEnable(GL11.GL_TEXTURE_2D);			GL11.glBindTexture(GL11.GL_TEXTURE_2D, prevGlowTexture);						GL11.glEnable(GL11.GL_BLEND);			GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);						GL11.glBegin(GL11.GL_QUADS);			{				GL11.glColor4f(1f, 1f, 1f, 0.5f);								float uMax = (1f / TEXTURE_WIDTH) * BLUR_WIDTH;				float vMax = (1f / TEXTURE_HEIGHT) * BLUR_HEIGHT;								GL11.glTexCoord2f(0f, 0f);				GL11.glVertex2f(0f, 0f);								GL11.glTexCoord2f(uMax, 0f);				GL11.glVertex2f(BLUR_WIDTH, 0f);								GL11.glTexCoord2f(uMax, vMax);				GL11.glVertex2f(BLUR_WIDTH, BLUR_HEIGHT);								GL11.glTexCoord2f(0f, vMax);				GL11.glVertex2f(0f, BLUR_HEIGHT);			}			GL11.glEnd();		*/									GL11.glColor4f(1f, 1f, 1f, 1f);			GL11.glDisable(GL11.GL_TEXTURE_2D);			GL11.glDisable(GL11.GL_BLEND);		}		postRender();	}			private void preRender()	{		GL11.glMatrixMode(GL11.GL_PROJECTION);		GL11.glLoadIdentity();				GL11.glOrtho(0, BLUR_WIDTH,	0, BLUR_HEIGHT,	-1f, 1f);				// Translate to position		GL11.glMatrixMode(GL11.GL_MODELVIEW);		GL11.glLoadIdentity();				GL11.glPushMatrix();	}		private void postRender()	{		GL11.glPopMatrix();	}}

This topic is closed to new replies.

Advertisement