Parallax mapping by marching


I had this idea thanks to the existance of raymarching. Currently figuring out self shadowing. Last half hour was messing with defines and such to get it to work on PS2.0, which it does now although I had to strip out the _Color and _SpecularColor in that version (althouh the properties are still defined the multiplications simply aren’t done).

Also seeing if maybe I need to make a PS3.0 version with loops instead of this hideous stack of if-checks. I just didn’t want to struggle with compiling so this was easier right now.

Shader "Custom/ParallaxMarching" 
{
	Properties 
	{
		_MainTex ("Base (RGBA)", 2D) = "white" {}
		_Color ("Color (RGBA)", Color) = (1,1,1,1)
		_NormalMap ("Tangent normals", 2D) = "bump" {}
		_HeightMap ("Height map (R)", 2D) = "white" {}
		_Intensity ("Intensity", Float) = 0.001
		
		_SpecularPower ("Specular power", Float) = 100
		_SpecularFresnel ("Specular fresnel falloff", Float) = 4
		_SpecularTex ("Specular texture (RGB)", 2D) = "white" {}
		_SpecularColor ("Specular color (RGB)", Color) = (1,1,1,1)
	}
	
	CGINCLUDE
	//only 4 steps in shader program 2.0
	//10 steps is max & prettiest, 2 steps is min
	#define PARALLAX_STEPS 4
	#define INTENSITYSCALE 5/PARALLAX_STEPS
	//#define SPECULAR_FRESNEL
	#define OPTIMIZE_PS20
	
	uniform sampler2D _MainTex;
	uniform half4 _Color;
	uniform sampler2D _NormalMap;
	uniform sampler2D _HeightMap;
	uniform half _Intensity;
	uniform half _SpecularPower;
	uniform half _SpecularFresnel;
	uniform sampler2D _SpecularTex;
	uniform half4 _SpecularColor;
	
	#include "BaseFunctions.cginc"
	
	half4 frag_parallax(v2f i) : COLOR
	{
		//get some normalized vectors
		half3 worldBiTangent = cross(i.worldTangent, i.worldNormal);
		half3 cameraDirection = normalize(i.worldPosition - _WorldSpaceCameraPos);
		
		//determine what the tangent space step is from this view angle
		half2 uvstep = half2( dot( cameraDirection, i.worldTangent ),
		  dot( cameraDirection, worldBiTangent ) ) * _Intensity;
		uvstep *= INTENSITYSCALE;
		
		//iteratively sample until a point is hit
		half2 uv = i.uv;
		
		#if PARALLAX_STEPS > 1
		half mapDepth0 = 1-tex2D(_HeightMap, uv).r;
		half mapDepth1 = 1-tex2D(_HeightMap, uv + uvstep).r;
		#endif
		#if PARALLAX_STEPS > 2
		half mapDepth2 = 1-tex2D(_HeightMap, uv + uvstep*2).r;
		#endif
		#if PARALLAX_STEPS > 3
		half mapDepth3 = 1-tex2D(_HeightMap, uv + uvstep*3).r;
		#endif
		#if PARALLAX_STEPS > 4
		half mapDepth4 = 1-tex2D(_HeightMap, uv + uvstep*4).r; 
		#endif
		#if PARALLAX_STEPS > 5
		half mapDepth5 = 1-tex2D(_HeightMap, uv + uvstep*5).r;
		#endif
		#if PARALLAX_STEPS > 6
		half mapDepth6 = 1-tex2D(_HeightMap, uv + uvstep*6).r;
		#endif
		#if PARALLAX_STEPS > 7
		half mapDepth7 = 1-tex2D(_HeightMap, uv + uvstep*7).r;
		#endif
		#if PARALLAX_STEPS > 8
		half mapDepth8 = 1-tex2D(_HeightMap, uv + uvstep*8).r;
		#endif
		#if PARALLAX_STEPS > 9
		half mapDepth9 = 1-tex2D(_HeightMap, uv + uvstep*9).r;
		#endif
		
		#if defined(STEPS_10)
		half depthStep = 0.1;
		#else
		half depthStep = 0.2;
		#endif
		
		#if PARALLAX_STEPS > 1
		if( mapDepth0 > 0 && mapDepth1 > depthStep )
		{
			uv = uv + uvstep;
			#if PARALLAX_STEPS > 2
			if( mapDepth2 > depthStep*2 )
			{
				uv = uv + uvstep*2;
				#if PARALLAX_STEPS > 3
				if( mapDepth3 > depthStep*3 )
				{
					uv = uv + uvstep*3; 
					#if PARALLAX_STEPS > 4
					if( mapDepth4 > depthStep*4 )
					{
						uv = uv + uvstep*4;
						#if PARALLAX_STEPS > 5
						if( mapDepth5 > depthStep*5 )
						{
							uv = uv + uvstep*5;
							#if PARALLAX_STEPS > 6
							if( mapDepth6 > depthStep*6 )
							{
								uv = uv + uvstep*6;
								#if PARALLAX_STEPS > 7
								if( mapDepth7 > depthStep*7 )
								{
									uv = uv + uvstep*7;
									#if PARALLAX_STEPS > 8
									if( mapDepth8 > depthStep*8 )
									{
										uv = uv + uvstep*8;
										
										#if PARALLAX_STEPS > 9
										if( mapDepth9 > depthStep*9 )
										{
											uv = uv + uvstep*9;
										}
										#endif
									}
									#endif
								}
								#endif
							}
							#endif
						}
						#endif
					} 
					#endif
				}
				#endif
			}
			#endif
		}
		#endif
		
		//apply normal mapping
		half3 N = half3(tex2D(_NormalMap, i.uv).ra*2.0-1.0, 1.0);
		N = normalize( mul( N, float3x3(i.worldTangent, worldBiTangent, i.worldNormal) ) );
		
		//implement some lighting
		half3 L = _WorldSpaceLightPos0.xyz;
		half atten = 1.0;
		#ifndef OPTIMIZE_PS20
		if( _WorldSpaceLightPos0.w == 1 )
		{
			L -= i.worldPosition;
		#endif
			#ifdef OPTIMIZE_PS20
		 	//multiplying the worldPosition by 0 costs less instructions
			L -= i.worldPosition * _WorldSpaceLightPos0.w;
			#endif
			//it does mean that for directional lights these calculations are all useless and slow down the shader
			half invLightDistance = 1.0 / length(L);
			L *= invLightDistance;
			atten *= invLightDistance;
		#ifndef OPTIMIZE_PS20
		}
		#endif
		half NdotL = max(0, dot(L, N));
		
		half3 R = reflect(cameraDirection, N);
		half RdotL = max(0, dot(R, L))*atten;
		RdotL = pow(RdotL, _SpecularPower);
		
		#ifdef SPECULAR_FRESNEL
		half FR = dot(cameraDirection, N);
		if( _SpecularFresnel < 0 )
			RdotL *= pow(1-FR, _SpecularFresnel);
		else
			RdotL *= pow(FR, _SpecularFresnel);
		#endif
		
		half4 outColor = NdotL * _LightColor0 * tex2D(_MainTex, uv)
#ifndef OPTIMIZE_PS20
		* _Color.xyz
#endif
;
		outColor.xyz += RdotL * _LightColor0.xyz * tex2D(_SpecularTex, uv).xyz
#ifndef OPTIMIZE_PS20
		* _SpecularColor.xyz
#endif
;
		return outColor;
	}
	ENDCG
	
	SubShader 
	{
		Tags { "RenderType"="Opaque" }
		
		Pass
		{ 
			Tags{ "LightMode" = "ForwardBase" }
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag_parallax
			#pragma target 2.0
			#pragma only_renderers d3d9 
			#pragma fragmentoption ARB_precision_hint_fastest 
			//required for lights to update
			#pragma multi_compile_fwdbase_fullshadows
			ENDCG
		}
	} 
	FallBack "Diffuse"
}

Applying decals

Creating a tool for applying decals is fairly useful…

I consider the case in which a decal is a single planar polygon with a texture (be it transparent, cutout or neither)

An artist would want to take the polygon (with texture) and parent it to the camera, then the artist can move around until a nice spot for the decal is found. In the meantime the decal can be panned and rotated (only around the camera forward axis) so it always remains in the same plane as the viewport.

Once the decal looks nice from the point of view of the camera, it needs to be applied in 3D and separated from the camera.

This initially is a straightforward action: for each vertex we need to shoot a ray from the camera position, through the vertex, onto our world or target mesh. The intersection point minus a little offset (to avoid coplanar faces which cause flickering) is where the vertex should be set.

Problems arise however when applying the decal on a curved surface, it will be floating in front of or penetrating through the surface…

This can be solved in another way. Fastest is if we know what mesh we wish to apply our decal on, otherwise we’d need to combine the world mesh for this and that gets heavy to compute really fast.

Transform the mesh to world space, then transform it to camera space (or the decal’s object space).
Now divide x,y by z to put the mesh onto the view plane, from this point on, our case is 2D.
Foreach edge in the mesh: check intersection with each edge in the decal polygon.
If they intersect, insert a vertex into the polygon (or schedule the insertion until all checks are performed).
When done inserting vertices (which in a simple single non-triangulated polygon case is easily accomplished by inserting a point at the right place in the list).
We can first merge the vertices with a small tolerance in case we graze the tip of a triangle and split redundantly much at some point.

Here we have an original vertices and three splits very close to eachother merging these will make the result cleaner and have less ugly normals.

Next things to do:
Raycast all the vertices in world space onto the mesh in world space as described before
Triangulate
Calculate normals (cross product 2 edges of each triangle: (p2-p0) X (p1-p0))
Unparent from the camera (if you wrote a tool that did that for the artist)
Done!

Note that it is possible to flatten the decal polygon in camera space as well, making that it does not need to be parented to the camera and a user is free to modify it in any way.

Delaunay Triangulation

dt

I was trying to cap polygons with holes and all my gaps were coplanar. As opposed to my initial solution (finding edge rings and capping those, leaving me with no clue how to cut out holes) I decided to look up how to go about this. This was really a learning exercise and it proved more complex than I initially anticipated.

So the idea, and some illustrations displaying that I wasn’t the first to run into the problem of capping edge rings come from here:
Jose Esfer

But the most difficulty I had with finding a good reference for this. There’s a lot of scientific papers that go very, very far beyond my understanding of mathematical jargon and there’s a number of source code examples in various languages, but the few I opened were lengthy so I knew the only way to understand this was with a decent explanation, finally to be discovered here:
Computing constrained Delaunay triangulations

This explanation is pretty clear although it took me over a day to implement, further errors are yet to be discovered…

The only thing to watch for is the explanation of the actual triangulation algorithm where the first ‘New LR-edge’ is inserted and some edge gets deleted one image yet is not deleted in the next (and in my implementation hence never deleted). So following what the text actually says got me there.

UPDATE: Added debug output -> define UDT_Debug in the conditional compilation symbols of the project settings (also for the editor project) and get some control on where to stop triangulating. It displays potential new connection candidates as circles (green and blue) as well as the last edge (red) and the last deleted edge(s) (green).

Also randomization now generates input when you check it and then unchecks itself.

I’m not sure if it’s useful to post the source I ended up with, because it’ll be just another example out there, but since it’s for Unity it may be easier to get it running. Do select the GameObject you attach it to though because it relies on OnDrawSceneGUI which only gets called for selected objects.

It displays the current vertex numbers as well, which may give some confusion because the vertices get dynamically rearranged so this does not reflect your actual input.

Grab the package here!

Unity meshes

Playing around with Unity and trying to generate a Mesh. But if it is not known in advance how large the vertex and triangle lists have to be, ArrayLists are a solution, which apparantly cannot just be cast into a mesh.

Instinctively one would do
mesh.vertices = new Vector3[vtxarray.Count]
vtxarray.CopyTo(mesh.vertices)
but this copies the data and does not display the Mesh.
Instead it has to be assigned to another Vector3[] variable, then this variable can be assigned to mesh.vertices; I tested assigning mesh.vertices to itself after copying but that also didn’t work.

Here’s a working Quad example:

using UnityEngine;
using System.Collections;


public class Quad : MonoBehaviour {
	void Start () {
		//add a mesh and meshrenderer to this transform
		gameObject.AddComponent();
		Mesh mesh = gameObject.AddComponent().mesh;
		
		ArrayList verts = new ArrayList();
		ArrayList uvs = new ArrayList();
		ArrayList tris = new ArrayList();

		//generate the meshdata
		verts.Add( new Vector3(-1, 0, -1) );
		verts.Add( new Vector3(1, 0, -1) );
		verts.Add( new Vector3(-1, 0, 1) );
		verts.Add( new Vector3(1, 0, 1) );
		uvs.Add( new Vector2(0, 0) );
		uvs.Add( new Vector2(1, 0) );
		uvs.Add( new Vector2(0, 1) );
		uvs.Add( new Vector2(1, 1) );
		tris.Add(0);
		tris.Add(1);
		tris.Add(2);
		tris.Add(2);
		tris.Add(1);
		tris.Add(3);
		
		// copy arraylists to arrays, then apply arrays to mesh
		// it's impossible to use CopyTo(mesh.variable) directly
		// although printing the data gives valid output, mesh
		// is invisible
		Vector3[] vertices = new Vector3[verts.Count];
		verts.CopyTo(vertices);
		mesh.vertices = vertices;
		Vector2[] uv = new Vector2[uvs.Count];
		uvs.CopyTo(uv);
		mesh.uv = uv;
		int[] triangles = new int[tris.Count];
		tris.CopyTo(triangles);
		mesh.triangles = triangles;
		
		mesh.RecalculateNormals();
		mesh.RecalculateBounds();
	}
	

	void Update () {
	
	}
}