Page 1 of 3

Software Edge Clipping With GL?

Posted: Sat Sep 27, 2014 11:54 am
by Baker
The software renderer doesn't have Z-fighting with brush models because it clips the edges of brush model to the world.

I've been thinking a little of trying to use this in a GLQuake engine.

I've been looking through the software renderer code, it seems like a somewhat largish challenge would be converting the clipped edges into polygons and getting the texcoords. It looks like the model loader provides all the information needed to do this.

[I'd also kind of like to get alpha-masked textures (fence-like textures) working in software, but there are some obstacles to that too ...]

Re: Software Edge Clipping With GL?

Posted: Sat Sep 27, 2014 4:36 pm
by Baker
A tighter R_RecursiveWorld node:

Code: Select all

void R_RecursiveWorldNode (mnode_t *node, int clipflags)
{
	int			i, c, side, *pindex;
	vec3_t		acceptpt, rejectpt;
	mplane_t	*plane;
	msurface_t	*surf, **mark;
	mleaf_t		*pleaf;
	double		d, dot;

	if (node->contents == CONTENTS_SOLID)
		return;		// solid

	if (node->visframe != cl.r_visframecount)
		return;

// cull the clipping planes if not trivial accept
// FIXME: the compiler is doing a lousy job of optimizing here; it could be
//  twice as fast in ASM
	if (clipflags)
	{
		for (i = 0 ; i < 4 ; i++)
		{
			if (! (clipflags & (1<<i)) )
				continue;	// don't need to clip against it

		// generate accept and reject points
		// FIXME: do with fast look-ups or integer tests based on the sign bit
		// of the floating point values

			pindex = pfrustum_indexes[i];

			rejectpt[0] = (float)node->minmaxs[pindex[0]];
			rejectpt[1] = (float)node->minmaxs[pindex[1]];
			rejectpt[2] = (float)node->minmaxs[pindex[2]];

			d = DotProduct (rejectpt, view_clipplanes[i].normal);
			d -= view_clipplanes[i].dist;

			if (d <= 0)
				return;

			acceptpt[0] = (float)node->minmaxs[pindex[3+0]];
			acceptpt[1] = (float)node->minmaxs[pindex[3+1]];
			acceptpt[2] = (float)node->minmaxs[pindex[3+2]];

			d = DotProduct (acceptpt, view_clipplanes[i].normal);
			d -= view_clipplanes[i].dist;

			if (d >= 0)
				clipflags &= ~(1<<i);	// node is entirely on screen
		}
	}

// if a leaf node, draw stuff
	if (node->contents < 0)
	{
		pleaf = (mleaf_t *)node;

		mark = pleaf->firstmarksurface;
		c = pleaf->nummarksurfaces;

		if (c)
		{
			for ( ; c ; c--, mark++)
			{
				if (!level.water_vis_known)
					Liquid_Think ((*mark)->flags & SURF_UNDERWATER);

				(*mark)->visframe = cl.r_framecount;
			}
		}

	// deal with model fragments in this leaf
		if (pleaf->efrags)
			R_StoreEfrags (&pleaf->efrags);

		pleaf->key = r_currentkey;
		r_currentkey++;		// all bmodels in a leaf share the same key
	}
	else
	{
	// node is just a decision point, so go down the appropriate sides

	// find which side of the node we are on
		plane = node->plane;

		switch (plane->type)
		{
		case PLANE_X:
			dot = modelorg[0] - plane->dist;
			break;
		case PLANE_Y:
			dot = modelorg[1] - plane->dist;
			break;
		case PLANE_Z:
			dot = modelorg[2] - plane->dist;
			break;
		default:
			dot = DotProduct (modelorg, plane->normal) - plane->dist;
			break;
		}

		if (dot >= 0)
			side = 0;
		else
			side = 1;

	// recurse down the children, front side first
		R_RecursiveWorldNode (node->children[side], clipflags);

	// draw stuff
		c = node->numsurfaces;

		if (c)
		{
			surf = cl.worldmodel->surfaces + node->firstsurface;

			if (dot < 0 -BACKFACE_EPSILON)
				side = SURF_PLANEBACK;
			else if (dot > BACKFACE_EPSILON)
				side = 0;
	
	
			for ( ; c ; c--, surf++)
			{
				if (surf->visframe != cl.r_framecount)
					continue;
	
				if (( (dot < 0) ^ !!(surf->flags & SURF_PLANEBACK)))
					continue; // wrong side
				
				if (!level.water_vis_known)
					Liquid_Think (surf->flags & SURF_UNDERWATER);
	
				R_RenderFace (surf, clipflags);
	
			} // End of for

			// all surfaces on the same node share the same sequence number
			r_currentkey++;
		} // End of if c

	// recurse down the back side
		R_RecursiveWorldNode (node->children[!side], clipflags);
	} // End of else node->contents < 0
}

Re: Software Edge Clipping With GL?

Posted: Sat Sep 27, 2014 6:39 pm
by qbism
Fence textures would be incredibly easy in a software engine that already generates its own alphamap tables. It's just a new table. Color index X let's the base color show through.
In terms of mapping use *fenceXXX texture naming and the engine can flag it. The compiler will treat it like water so have to clip brush around it. I can't remember if typical compiler treats all * as water.
I did a similar thing for *glass.

Re: Software Edge Clipping With GL?

Posted: Sun Sep 28, 2014 1:37 am
by mankrip
The software renderer doesn't actually clip the edges. It doesn't generate new edges, and it doesn't generate new vertices, so there's no way to use it to reshape the brushes.

The clipping is done at the spans level. It's kinda like a run-lenght encoded Z-buffer check: each span is segmented by the nearer span segments of other planes.

To port this to the GL renderer, a large portion of the software renderer would have to be used, all the way to the spans generation. And then, a fragment shader could be used to make the GPU only draw the pixels that matches the corresponding spans from the software renderer.

Re: Software Edge Clipping With GL?

Posted: Sun Sep 28, 2014 3:31 am
by Baker
mankrip wrote:The software renderer doesn't actually clip the edges. It doesn't generate new edges, and it doesn't generate new vertices, so there's no way to use it to reshape the brushes.
Ah that sucks. The functions have names like R_ClipEdge. :(

Re: Software Edge Clipping With GL?

Posted: Sun Sep 28, 2014 1:10 pm
by leileilol
that's more for preventing from drawing past the refdef's edges iirc

Re: Software Edge Clipping With GL?

Posted: Sun Sep 28, 2014 2:26 pm
by Baker
leileilol wrote:that's more for preventing from drawing past the refdef's edges iirc
Hmmm. I still insist on "no z-fighting" but this means 2015. I'll probably have to crack open the BSP compiler, get down with windings and CW vs. CCW, plane calcs and clip them against the world model myself. Which is going to depend on efrags, which can be troublesome ...

Very few brushes z-fight, but some of the ones that do, there is no acceptable solution. I was hoping to leverage the rarity of the situation.
qbism wrote:Fence textures would be incredibly easy in a software engine that already generates its own alphamap tables. It's just a new table. Color index X let's the base color show through.
In terms of mapping use *fenceXXX texture naming and the engine can flag it. The compiler will treat it like water so have to clip brush around it. I can't remember if typical compiler treats all * as water.
I did a similar thing for *glass.
A sensible map would only use 'fence textures' on brush model entities. That being said, Kurok and Rubicon Rumble both use alpha masked textures on the world, at least a little.

I say sensible because alpha masked textures on the world is going to show void, which without glClear or some equivalent is going to do render HOM.

What you said about alpha masked textures and alpha maps, I haven't quite yet dug deep enough into rendering to determine even how to do .alpha entities yet. Probably a few days out.

Re: Software Edge Clipping With GL?

Posted: Sun Sep 28, 2014 4:00 pm
by qbism
Via texture naming the compiler will treat fence textures like water, so won't see void. But will also inherit whatever limitations water brushes have.
Brush models are the 'hard way'' to implement but are more flexible and easier to map. Alpha is implemented this way in Makaqu in a robust way in terms of proper sorting and layered transparency. The only SW engine AFAIK that renders backtoforward correctly.

Re: Software Edge Clipping With GL?

Posted: Tue Oct 07, 2014 2:25 am
by Knightmare
Baker wrote:A tighter R_RecursiveWorld node:

Code: Select all

void R_RecursiveWorldNode (mnode_t *node, int clipflags)
{
	.
	.
	.
		if (c)
		{
			.
			.
			.	
	
			for ( ; c ; c--, surf++)
			{
				if (surf->visframe != cl.r_framecount)
					continue;
	
				if (( (dot < 0) ^ !!(surf->flags & SURF_PLANEBACK)))
					continue; // wrong side
				
				if (!level.water_vis_known)
					Liquid_Think (surf->flags & SURF_UNDERWATER);
	
				R_RenderFace (surf, clipflags);
	
			} // End of for

			// all surfaces on the same node share the same sequence number
			r_currentkey++;
		} // End of if c

	// recurse down the back side
		R_RecursiveWorldNode (node->children[!side], clipflags);
	} // End of else node->contents < 0
}
It's not a good idea to call your render function from inside R_RecursiveWorldNode(), as it leads to pipeline stalls. You should build a linked list of surfaces there instead, and render it after the BSP tree traversal. This latter approach better lends itself to keeping the GPU's pipeline full.

Ignore this if your R_RenderFace() just adds the surface to a list for later rendering.

I changed Q2's and Daikatana's multitexture rendering path to use texturechains (what Q2's ref_gl calls its linked lists of surfaces), and got a significant performance improvement, especially on ATI/AMD and Intel hardware. Batching dynamic light updates in multitexture (as in MH's enhanced Q2 code) also yielded a similar improvement when dynamicly lit surfaces are present.

Re: Software Edge Clipping With GL?

Posted: Tue Oct 07, 2014 12:26 pm
by mh
It's also the case that texturechains built from R_RecursiveWorldNode (using the stock code) are in back-to-front order, which is not optimally friendly for modern GPUs. You can reverse the chains before drawing (I suggest reversing into per-lightmap chains) or chain in front-to-back order instead (see JoeQuake for one way of doing it) which can help get the performance up a little more.

Re: Software Edge Clipping With GL?

Posted: Tue Oct 07, 2014 5:52 pm
by Baker
Knightmare wrote:It's not a good idea to call your render function from inside R_RecursiveWorldNode(), as it leads to pipeline stall
The code above is in WinQuake, the software renderer in r_bsp.c. Note the R_RenderFace, which is a function in r_draw.c and the comment "// FIXME: the compiler is doing a lousy job of optimizing here; it could be twice as fast in ASM". GLQuake doesn't use ASM for drawing.

I was hoping to reverse out how it clips edges in brush model entity polygons against the world, but it doesn't work the way I hoped. WinQuake doesn't Z-fight brush model entities vs. the world, I was hoping to unlock this mechanism for use in GL, but looks like this was a mostly a dead lead ...

Re: Software Edge Clipping With GL?

Posted: Wed Oct 08, 2014 3:48 am
by mankrip
Baker wrote:I was hoping to reverse out how it clips edges in brush model entity polygons against the world, but it doesn't work the way I hoped. WinQuake doesn't Z-fight brush model entities vs. the world, I was hoping to unlock this mechanism for use in GL, but looks like this was a mostly a dead lead ...
Well, as I said before, you could probably reintegrate the software BSP renderer back in the engine, and use its spans on a fragment shader as a whitelist of pixels that should be rendered for each surface.

Alternatively, you could also just use it to skip submodel BSP faces entirely; if the edges being processed are from a BSP submodel, and no spans are generated, skip the rendering of its corresponding surface in the GL renderer. This should take care of most situations.

_____________________________


On an unrelated note, it doesn't feel right for me to use asterisk-named textures for "fence" textures. The natural way would be using textures with alphamasked texels (color 255) or an alpha channel.

Compiling such surfaces as turbulent surfaces brings up some issues too complicated to be worth it:
- No lightmap
- Non-solid faces
- pointcontents set to water
Using invisible func_wall entities and the like to make them seem solid would be an awkward solution. It's counter-intuitive, and the mapper shouldn't have such an extra burden brought upon him/her.

It's easier to just make a modified VIS compiler to make surfaces with (semi)transparent texels become see-through like water.

The "grey void"/HOM effect problem won't happen on surfaces whose alphamasked texels aren't close to their edges and when they're created in places that the player can't look at from extreme angles.

Using BSP submodels with alphamasked textures is the easiest solution, but it may be unnecessarily slow if not optimized properly. Only their surfaces with alphamasked textures should be treated as see-through; the rest shouldn't.

Also, it would be nice to make them solid to the player, while allowing projectiles such as rockets and grenades to pass through them. This happens in Doom, but I don't remember if such mechanics are present in the vanilla engine.

Re: Software Edge Clipping With GL?

Posted: Wed Oct 08, 2014 6:18 am
by Baker
mankrip wrote:Alternatively, you could also just use it to skip submodel BSP faces entirely; if the edges being processed are from a BSP submodel, and no spans are generated, skip the rendering of its corresponding surface in the GL renderer. This should take care of most situations.
If you load Warpspasm in a software renderer, in the starting rooms there are health boxes situated at an angle. And WinQuake doesn't actually render them right (I should have considered this a clue it wasn't clipping the edges in the literal sense.)

Since the majority of brush models might as well be simple boxes, when time permits I'd like to experiment with the idea of literally clipping the edges mostly because it will give me extra practice with 3D calculations.

And should solve case the Warpspasm case too.

Re: Software Edge Clipping With GL?

Posted: Wed Oct 08, 2014 11:26 am
by mankrip
WarpSpasm runs in WinQuake? I've got to try it.

Re: Software Edge Clipping With GL?

Posted: Wed Oct 08, 2014 5:58 pm
by mh
mankrip wrote:On an unrelated note, it doesn't feel right for me to use asterisk-named textures for "fence" textures. The natural way would be using textures with alphamasked texels (color 255) or an alpha channel.
The reason for the choice of '{' (not '*') for these was because it was what HL1 used. It felt more right to copy some prior art. Integration also becomes easier if you want your engine to also support HL1 maps (you've only one code-path to write).