Trying to achieve a fresnel effect; incorrect result thus far (Horizon Fresnel?)

Greetings,
i’ve been really really really busy trying to make my own “Rainshader” or simply put,
a normal-mapped-specular shader, which also has the ability to use depthmaps and some script assigned values to determine where it should display an effect that ends up like rain.

And i’m really far, but there’s one obstacle that’s been punching my face the past two afternoons.

I used the well-known MirrorReflection script (http://wiki.unity3d.com/index.php?title=MirrorReflection2) and shader parts and added them into the shader for achieving the reflections cast on the water surfaces, but I want to apply a fresnel effect to these, too.

I’ve used the basis of the rim-shader, as i thought the effect that causes the rim would be the same as a normal fresnel equation.
albedo is a fixed4, determined further up in the code.
refl is the reflection map, as handed over by the mirrorreflection

half fresnel = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
o.Albedo = albedo+(refl*(pow (fresnel, _FresnelPower)))

But the result I end up with, is the same as you can see on the webplayer here:
(Dont note the project name, this is just another test scene i dumped in a test project i once named Occlusion culling ;p )
http://annihlator.nl/RainTest/Web/Web.html
if I understand correct, fresnel is supposed to be calculated from your viewing angle to the surfaces angle… why does it not seem to be happening here? when i turn in some directions (as in, North East South West), it’s as if the fresnel completely does not excist.

It’s most clearly seen when you’re standing on the darkest surface (rock) and face the opposing (concrete), in that direction i don’t witness ANY fresnel. when standing exactly the other way around… the fresnel seems way too active (to me, but this may be a matter of tweaking afterwards)

Its almost like the angle calculation is… rotated?
does anyone have a pointer?

PS: the shader employs Shader Model 3.0 and is highly unoptimized and thus may turn out to be very slow or even incompatible with some systems! (optimisation comes after this technical tidbit’s finished :slight_smile: )

PPS: yes, i know the textures have minor seams.
PPPS: also windows clients available, check: www.annihlator.nl/RainTest

Shameless bump and minor update note:
the scene now also has controls for manually controlling rain levels!
In-game FPS counter added!
FPS has gained a rough 10% on first round of optimisation.

Please note again that this scene intentionally is highly unoptimized, so performance drawbacks should be more noticable (atleast, that’s my theory :slight_smile: )

Hi. Nice looking project by the way.

Your math looks correct for the fresnel term so Im guessing the problem is with the viewDir or normal.

Ive had a similar problem using IN.viewDir. I wasnt getting the results I expected. In the end I manually calculated it like this.

float3 viewDir = normalize(_WorldSpaceCameraPos-IN.worldPos);

This worked fine for me. Never did figure out why using IN.viewDir didnt work.

1 Like

Thanks for the suggestion Scrawk! Sadly i can’t test this yet since i’m stuck at school with hardware that supports up to Shader Model 2.0 only (DARNIT! Remote desktop is too slow for these kind of things too, haha.)

This brings me to two other questions though i’d like to ask, since it’s still relevant to this shader:

I want to write a lighter version of this shader for Shader Model 2.0 support, which brings me to the following questions:

  • When setting Shader LOD, will unity use this to determine in any way which shader to choose, meaning that if the LOD treshold is not suitable for the shader it’d go to the fallback shader? or what else happens if the shader is over the current LOD treshold allowed by quality settings?
    (I’m planning to write this shader with a LOD level of 1600, and then a sm2.0 version i guess with an LOD level of 600 or 800, shaders would be aptly named “Rainshader HQ” and “Rainshader LQ”)

  • If i would rewrite this shader to a cg multipass shader, can i use a total of two passes each assigning different texture interpolators? (so basically dividing my current single pass to somekind of one-and-a-half-pass method?) (I still need to learn how to write a cg shader properly, but if i can achieve my goal that way i guess i’d better get to that then :slight_smile: )

EDIT: Managed to use remote desktop to test the piece of code, but the result is exactly the same…
However, when i change the Renderpath from Deferred (what i normally use) to forward, the fresnel calculation seems to suddenly work more “as-should”
Does using deferred mode influence the variabled returned by IN.worldPos or o.Normal in any way?
I’m using the standard BlinnPhong lighting by the way…

How are you calculating the view direction in your vertex shader?

Sounds like you’re getting the dot product of a viewDir in object (or world) space against a normal in tangent space.

The spaces will need to be the same for the dot product to work. Otherwise they will end up looking rotated, yes.

This should be all pieces of relevant code in my surface shader considering the normals / viewdir:

SubShader 
{ 
	Tags { "RenderType"="Opaque" }
	LOD 100
	
CGPROGRAM
#pragma surface surf BlinnPhong addshadow
#pragma target 3.0
#include "UnityCG.cginc"

struct Input {
	float2 uv_SplashMap;
	float2 uv_MainTex;
	float2 uv_BumpMap;
	half3 worldPos;
	float4 screenPos;
	//float3 viewDir;
	INTERNAL_DATA
};

void surf (Input IN, inout SurfaceOutput o){
    // viewDir hack as suggested by Scrawk
    float3 viewDir = normalize(_WorldSpaceCameraPos-IN.worldPos);
    fixed3 norm = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
    fixed3 splash = UnpackNormal(tex2D(_SplashMap, SplashUV));
    o.Normal = normalize(norm + (splash*Rainproperties.r*2));
    half fresnel = 1.0 - saturate(dot (normalize(viewDir), o.Normal));
    o.Albedo = albedo+(refl*(pow (fresnel, _FresnelPower)));
}
ENDCG
}

(viewDir is now commented out for testing with the following hack as suggested: )

And i think your suspicion is correct, basically i’m currently grabbing my normal from my output channel right now…

So i guess my viewDir angle then right now is in worldspace, and the normal i’m using in tangent space? (atleast… normal maps, as in the textures, are in tangent space, right?) so then i’d have to find how to get the worldspace normal…

I was assuming the viewDir is not changing appropriately for it didn’t matter in which way i’d rotate the camera… rotating the object also didnt seem to give any differences in shading…

I’m now trying to add IN.worldNormal instead of o.normal and am now first fighting a “too many texture interpolator” error, grr.
(How does a IN.worldNormal reference add an extra texture interpolator?)

Yeah, the tangent to world rotation can be expensive there as it’s done in the pixel shader and requires 3 interpolators to form the matrix. And I think viewDir is in worldspace inside of the surf function (it’s in tangent space inside of the lighting function, though).

Easiest option would be to turn the viewDir into tangent space inside of a vertex shader. You’ll need to normalize it inside of the pixel shader otherwise you can get odd shading due to interpolation. No need for INTERNAL_DATA as you’re not using any as far as I can tell.

Think this should do it;

SubShader
{ 
	Tags { "RenderType"="Opaque" }
	LOD 100

	CGPROGRAM
		#pragma surface surf BlinnPhong addshadow vertex:myvert
		#pragma target 3.0
		#include "UnityCG.cginc"

		struct Input {
			float2 uv_SplashMap;
			float2 uv_MainTex;
			float2 uv_BumpMap;
			half3 worldPos;
			float4 screenPos;
			float3 viewDirT;
		};

		void myvert (inout appdata_full v, out Input data) {
			UNITY_INITIALIZE_OUTPUT(Input,data);
			TANGENT_SPACE_ROTATION;
			data.viewDirT = mul(rotation, ObjSpaceViewDir(v.vertex));
		}

		void surf (Input IN, inout SurfaceOutput o){
			fixed3 norm = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
			fixed3 splash = UnpackNormal(tex2D(_SplashMap, SplashUV));
			o.Normal = normalize(norm + (splash*Rainproperties.r*2));
			half fresnel = 1.0 - saturate(dot (normalize(IN.viewDirT), o.Normal));
			o.Albedo = albedo+(refl*(pow (fresnel, _FresnelPower)));
		}
	ENDCG
}

You are working on too much detail here, search for “texture bombing” in google that will lead you to simple rain drops on surfaces.
Oblivion, Skyrim and many more professional games use this simple stuff.

Got much further now indeed!
In some angles the fresnel is still incorrect, but now it’s atleast object-orientation-related, so rotating the plane on its y-axis (facing upward in my situation) now also influences how the fresnel responds…

I believe this is because viewDir(T) is now updating correctly, now to change to comparison to the normal (I’m currently using the way of comparison suggested by FarFarer)

If i understand correctly, viewDirT should be in tangential space, and as such i’d not expect odd behaviour when comparing against o.normal , sadly there is again odd behaviour (SIGH)

I’m currently googling, and ran into another implementation of normal calculation in another shader on this url:
http://dorumon.googlecode.com/svn/trunk/New%20Unity%20Project%20Terrain/Assets/Shaders/WaterShader.shader

found in vert part:

v2f vert_full (appdata_full v)
{
	v2f o;
					
	v.vertex.xyz += vertexOffsetObjectSpace(v);
					
	o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);		
	o.projPos = ComputeScreenPos(o.vertex);
					
	o.viewDirWorld = -WorldSpaceViewDir(v.vertex);
					
	TANGENT_SPACE_ROTATION;
	o.TtoW0 = mul(rotation, _Object2World[0].xyz * unity_Scale.w);
	o.TtoW1 = mul(rotation, _Object2World[1].xyz * unity_Scale.w);
	o.TtoW2 = mul(rotation, _Object2World[2].xyz * unity_Scale.w);				
					
	return o;
}

found in fragment part:

// calculate world normal
	half3 normal = ((UnpackNormal(tex2D(_HeightTex,i.texcoord2))));
	half3 worldNormal;
	worldNormal.x = dot(i.TtoW0, normal.xyz);
	worldNormal.y = dot(i.TtoW1, normal.xyz);
	worldNormal.z = dot(i.TtoW2, normal.xyz);	
	
	worldNormal = normalize(worldNormal);

// FRESNEL CALCS
	float fcbias = 0.20373;
	float facing = saturate(0.8 - max(dot(-i.viewDirWorld,worldNormal), 0.0));
	float refl2Refr = max(fcbias + (1.0-fcbias) * pow(facing, _FresnelPower), 0);

I’m going to take a small leap and try to implement these parts of code, just hope it won’t cause my shader to go over it’s instruction limit :slight_smile:

@Aubergine
I’m not looking into texture bombing right now, i’m looking into having a fresnel calculation affect my reflection layer. I have saved the page though, for it is what i’d be looking for next to be able to replace my “ripple animation” script.
The difference being that i want to achieve the ability to simulate flooding of certain surfaces more “truthfully”, against repetition i will include a few world-coördinate-based modifications, but i haven’t implemented this (nor Vertex painting which i will use for mapping out overhangs and such) to make it far more easier to solve problems at this stage, taking things one step at a time.

My error seems to be that i’m comparing the different spaces wrongly, i’m trying hard to solve that :slight_smile:

That code you’ve found is what happens when you use

float3 worldNormal;
INTERNAL_DATA

in your input struct and then use

WorldNormal(IN, o.Normal) in your surface shader. You’ve already said that blows your interpolator limit.

What happens if you ditch all your other stuff and simply output only the fresnel value to the screen? Much easier to debug that way.

Also, if you post your entire shader code, it’s much easier for us to work stuff out. Snippets of code don’t mean a lot.

Additionally, why are you adding the reflection to the albedo texture? You’d want it as the Emission output, otherwise it will be edited by the diffuse light shading (i.e. any shadows or darkness due to shading will multiply out your reflection to nothing).

@Farfarer

ah darn, i was affraid it was the same… Thanks for telling me :slight_smile:

I didn’t want to post the full shader source for this since it (was/is) so untidy.
I have been busy tidying up the code and (thanks for reminding me!) now correctly assigned reflection to the o.Emission channel.

Full shader code currently is as follows:

–Shader code has been removed, sorry. –

Added the two scripts i use for reflection and ripple animation too, to make things easier.

Some parts may be unnecessary or repititive, then i just didn’t spot (with exception of Rainproperties.b, i’ve inverted it this way on purpose since i want to keep these maps still seperate at this stage, i have more plans with Rainproperties.b) it yet.
I plan on simplifying the math more when the shader appears exactly how i like, in this state i find it easier to trace back my own math :slight_smile:

Well I just tried that shader code and the fresnel value is fine.

I disabled forwardadd and wired fresnel directly to the o.Albedo, with a custom lighting function that spits out the unmodified s.Albedo. Just so I could view the raw fresnel value.

I can’t see anything wrong with it.

I just noticed something strange…
Only the parts which have been overlapped by the rain ripples have this strange fresnel reaction…
and then i converted my normal map to “Convert from greyscale” and all problems magically disappeared!.. so something’s wrong with my normal map itself, yaaay.

Thanks for your help and tons of patience guys, i’ll let you know when i have fixed my normalmap -.-

Edit: Does anyone happen to know why unity seems to think in some way my normal-map is “y-positive” i actually rendered a normal map like i always do, using cinema4D and haven’t ran into this problem (even though i’ve never rendered a normal map before that was supposed to be mainly flat).
Meanwhile i’ll just try filtering the r,g and b channels seperately to see what removes this strange y-offset.

Hey guys, could you help me in adding fresnel to this shader?

Shader "Custom/RiverWater" {
	Properties {
		_MainCol ("Color(RGB), Specular(A)", Color) = (1,1,1,0.5)
		_Power ("Specular Power", Range(0.01,1)) = 0.5
		_SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)
		_Normals ("NormalMap", 2D) = "white" {}
		_Normals2 ("WaveMap", 2D) = "white" {}
		_RefractTex("InternalRefraction", 2D) = "grey" {}
		_ReflectTex("Water", 2D) = "grey" {}
		_ReflectTex2("Water 2", 2D) = "grey" {}
		_Parallax ("Refraction Distort", Range (0.005, 0.08)) = 0.02
		_Alpha("Alpha(for non-refractive water)", range(0,1)) = 0.5
	}
	SubShader {
		Tags { "Queue" = "Transparent" "RenderType"="Transparent" }
		ZWrite on Cull off
		LOD 400
		
		CGPROGRAM
		#pragma surface surf BlinnPhong
		#pragma target 3.0
		
		fixed4 _MainCol;
		sampler2D _Normals;
		sampler2D _Normals2;
		sampler2D _RefractTex;
		sampler2D _ReflectTex;
		sampler2D _ReflectTex2;
		half _Parallax;
		half _Power;
		half _Alpha;
		
		struct Input {
			float2 uv_Normals;
			float2 uv_Normals2;
			float2 uv_RefractTex;
			float2 uv_ReflectTex;
			float2 uv_ReflectTex2;
			float4 screenPos;
			float3 viewDir;
		};

		void surf (Input IN, inout SurfaceOutput o) {			
			half h = tex2D (_Normals, IN.uv_Normals).w;
			half v = tex2D (_Normals2, IN.uv_Normals2).w;
			half2 offset = ParallaxOffset (h, _Parallax, IN.viewDir);
			half2 offset2 = ParallaxOffset (v, _Parallax, IN.viewDir);
			float4 col = tex2D (_ReflectTex, IN.uv_ReflectTex + (offset * offset2));
			float4 col2 = tex2D (_ReflectTex2,IN.uv_ReflectTex2 + offset);
			
			o.Albedo = _MainCol.rgb * _MainCol.a;
			o.Gloss = _MainCol.a * 8;
			o.Specular = _Power;
			o.Normal = UnpackNormal (tex2D (_Normals, IN.uv_Normals)) * UnpackNormal (tex2D (_Normals2, IN.uv_Normals2));
			float2 screenUV = (IN.screenPos.xy / IN.screenPos.w);
			screenUV += offset;
			screenUV *= float2(1,1);
			o.Albedo *= tex2D (_RefractTex, screenUV).rgb * 2;
			o.Albedo *= col.rgb;
	    	}	
		ENDCG
	} 
}

note that this is a water shader. I am attempting to add fresnel for more realism in the water.