Problem with the normals on water shader.
Asked Answered
S

7

0

Using the example in the godot manual I created a water shader. But I cannot get the normals to work right.

shader_type spatial;
render_mode specular_toon;

uniform float height_scale = 0.5;
uniform sampler2D noise;
uniform sampler2D normalmap;
uniform vec3 watercol;
uniform float watermet=0.0;
uniform float waterrough=0.05;
uniform float waterspeed=0.001;
uniform float waterrim=0.1;
uniform float wateralpha=0.7;

varying vec2 tex_position;

void vertex() {
  tex_position = (VERTEX.xz / 2.0 + 0.5)*TIME*waterspeed;

  float height = texture(noise, tex_position).x;
  VERTEX.y += height * height_scale;

  NORMAL = texture(normalmap, tex_position).xyz;
}

void fragment() {
	float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));	
	ALBEDO=watercol+(0.1 * fresnel);
	ROUGHNESS=waterrough* (1.0-fresnel);
	METALLIC=watermet;
	RIM=waterrim;
	ALPHA=wateralpha;
}

Shon answered 12/11, 2022 at 15:15 Comment(0)
F
0

There are ways to schlepp calculations from one vertex shader run to another, but they are more involved with textures as intermediate storage and multiple passes, and I assume not what you are looking for.

One way to have normals in the shader is to build them in the application from averages of the adjacent face normals. This is classical flat shading and needs pre-processing. I assume your "normalmap" uniform is exactly that. But since the water surface moves, you'd have to calculate that map every frame, which is too slow. Also, the application doesn't know about the movement since that happens in the vertex shader. I think that is the reason why it looks so "strange".

But you can also calculate normals in the vertex shader by looking up the values east, west, north and south of your vertex (careful at the edges which may need special treatment) and averaging them. Specifically interesting for height maps since it is very fast and doesn't require pre-processing. Here's an example in glsl (credits to Cozzy/Ring: 3D Engine Design for Virtual Globes):

vec3 calculateNormal( vec2 uv ) {
	vec2 texelSize = g_heightmapTextureInfo.zw;
	float n = sampleHeightmap( uv + vec2( 0.0f, -texelSize.x ) );
	float s = sampleHeightmap( uv + vec2( 0.0f, texelSize.x ) );
	float e = sampleHeightmap( uv + vec2( -texelSize.y, 0.0f ) );
	float w = sampleHeightmap( uv + vec2( texelSize.y, 0.0f ) );
	vec3 sn = vec3( 0.0f , s - n, -( texelSize.y * 2.0f ) );
	vec3 ew = vec3( -( texelSize.x * 2.0f ), e - w, 0.0f );
	sn *= ( texelSize.y * 2.0f );
	ew *= ( texelSize.x * 2.0f );
	sn = normalize( sn );
	ew = normalize( ew );
	vec3 result = normalize( cross( sn, ew ) );
	return result;
}

texelSize is a vec2(1/width,1/height) and passed in as a uniform.
sampleHeightmap simply samples the texture or texture level in case of mip maps and applies some corrections that may be necessary.

I had very interesting results with a sobel filter, a 3*3 matrix that contains weights for the adjacent points. This is very interesting when you want to experiment with the contrast dynamically while rendering. You would have to look up 8 points around your vertex, apply the weights from the filter, and average them. May need some experimenting.

Fitton answered 12/11, 2022 at 17:18 Comment(0)
S
0

Is there a way to store the VERTEX in the vertex function to be used in the next vertex function call?

Shon answered 12/11, 2022 at 16:23 Comment(0)
F
0

There are ways to schlepp calculations from one vertex shader run to another, but they are more involved with textures as intermediate storage and multiple passes, and I assume not what you are looking for.

One way to have normals in the shader is to build them in the application from averages of the adjacent face normals. This is classical flat shading and needs pre-processing. I assume your "normalmap" uniform is exactly that. But since the water surface moves, you'd have to calculate that map every frame, which is too slow. Also, the application doesn't know about the movement since that happens in the vertex shader. I think that is the reason why it looks so "strange".

But you can also calculate normals in the vertex shader by looking up the values east, west, north and south of your vertex (careful at the edges which may need special treatment) and averaging them. Specifically interesting for height maps since it is very fast and doesn't require pre-processing. Here's an example in glsl (credits to Cozzy/Ring: 3D Engine Design for Virtual Globes):

vec3 calculateNormal( vec2 uv ) {
	vec2 texelSize = g_heightmapTextureInfo.zw;
	float n = sampleHeightmap( uv + vec2( 0.0f, -texelSize.x ) );
	float s = sampleHeightmap( uv + vec2( 0.0f, texelSize.x ) );
	float e = sampleHeightmap( uv + vec2( -texelSize.y, 0.0f ) );
	float w = sampleHeightmap( uv + vec2( texelSize.y, 0.0f ) );
	vec3 sn = vec3( 0.0f , s - n, -( texelSize.y * 2.0f ) );
	vec3 ew = vec3( -( texelSize.x * 2.0f ), e - w, 0.0f );
	sn *= ( texelSize.y * 2.0f );
	ew *= ( texelSize.x * 2.0f );
	sn = normalize( sn );
	ew = normalize( ew );
	vec3 result = normalize( cross( sn, ew ) );
	return result;
}

texelSize is a vec2(1/width,1/height) and passed in as a uniform.
sampleHeightmap simply samples the texture or texture level in case of mip maps and applies some corrections that may be necessary.

I had very interesting results with a sobel filter, a 3*3 matrix that contains weights for the adjacent points. This is very interesting when you want to experiment with the contrast dynamically while rendering. You would have to look up 8 points around your vertex, apply the weights from the filter, and average them. May need some experimenting.

Fitton answered 12/11, 2022 at 17:18 Comment(0)
S
0

I have been trying to make a water shader for over one full year now. But it's always the same error.

shader_type spatial;
//render_mode specular_toon;

uniform float height_scale = 10.0;
uniform sampler2D noise;
uniform vec3 watercol;
uniform float watermet=0.0;
uniform float waterrough=0.05;
uniform float waterspeed=0.001;
uniform float waterrim=0.1;
uniform float wateralpha=0.75;
uniform float texsize=0.01;

varying vec2 tex_position;

vec3 getnormal(vec3 p1,vec3 p2,vec3 p3){
	vec3 r;
	
	vec3 u=p2-p1;
	vec3 v=p3-p1;
	
	r.x= (u.y*v.z) - (u.z*v.y);
	r.y= (u.z*v.x) - (u.x*v.z);
	r.z= (u.x*v.y) - (u.y*v.x);
	
	return r;
}


void vertex() {
  tex_position = (VERTEX.xz / 2.0 + 0.5);// * TIME * waterspeed;
	tex_position.x=tex_position.x+TIME*waterspeed;
  float height = texture(noise, tex_position).x;
  
	vec2 s=tex_position;
	s.y-=texsize;
	
	vec2 e=tex_position;
	e.x+=texsize;

	vec3 sn=VERTEX;
	vec3 ew=VERTEX;

	VERTEX.y=height*height_scale;
	
	sn.y=height_scale*texture(noise,s).x;
	ew.y=height_scale*texture(noise,e).x;

	sn.z+=texsize;
	ew.x+=texsize;	
	
	NORMAL=getnormal(VERTEX,sn,ew);

}

void fragment() {
	float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));	

	ALBEDO=watercol+(0.1 * fresnel);
	ROUGHNESS=waterrough* (1.0-fresnel);
	METALLIC=watermet;
	RIM=waterrim;
	ALPHA=wateralpha;

}
Shon answered 12/11, 2022 at 19:56 Comment(0)
S
0

I rewrote the shader,

//render_mode specular_toon;

uniform float height_scale = 50.0;
uniform sampler2D noise;
uniform vec3 watercol;
uniform float watermet=0.0;
uniform float waterrough=0.05;
uniform float waterspeed=0.001;
uniform float waterrim=0.1;
uniform float wateralpha=0.75;

vec3 getnormal(vec3 p1,vec3 p2,vec3 p3){
	vec3 r;
	
	vec3 u=p2-p1;
	vec3 v=p3-p1;
	
	r.x= (u.y*v.z) - (u.z*v.y);
	r.y= (u.z*v.x) - (u.x*v.z);
	r.z= (u.x*v.y) - (u.y*v.x);
	
	return r;
}


void vertex() {

	float thetexsize=1.0/1024.0;
	vec2 e=UV;
	e.x+=thetexsize;
	
	vec2 w=UV;
	w.x-=thetexsize;
	
	vec2 n=UV;
	n.y-=thetexsize;
	
	vec2 s=UV;
	s.y+=thetexsize;

	vec3 pc=VERTEX;
	vec3 pe=VERTEX;
	pe.x+=1.0;
	
	vec3 pw=VERTEX;
	pw.x-=1.0;
	
	vec3 pn=VERTEX;
	pn.z-=1.0;
	
	vec3 ps=VERTEX;
	ps.z+=1.0;

	pc.y=texture(noise,UV).b*height_scale;
	pe.y=texture(noise,e).b*height_scale;
	pw.y=texture(noise,w).b*height_scale;
	pn.y=texture(noise,n).b*height_scale;
	ps.y=texture(noise,s).b*height_scale;
	
	vec3 nor=getnormal(pc,pe,pn);
	nor=(nor+getnormal(pc,ps,pe))*0.5;
	nor=(nor+getnormal(pc,pw,ps))*0.5;
	nor=(nor+getnormal(pc,pn,pw))*0.5;
	
	nor=normalize(nor);
	NORMAL=nor;
	VERTEX=pc;
	
}

void fragment() {
	float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));	

	ALBEDO=watercol+(0.1 * fresnel);
	ROUGHNESS=waterrough* (1.0-fresnel);

	METALLIC=watermet;
	RIM=waterrim;
	ALPHA=wateralpha;

}

It is incorrect, but this is as close as I can make it.

Shon answered 13/11, 2022 at 1:14 Comment(0)
S
0

I think I have the normal calculation working.

shader_type spatial;
//render_mode specular_toon;

uniform float height_scale = 50.0;
uniform sampler2D noise;
uniform vec3 watercol;
uniform float watermet=0.0;
uniform float waterrough=0.05;
uniform float waterspeed=0.001;
uniform float waterrim=0.1;
uniform float wateralpha=0.75;
varying float ani;

vec3 getnormal(vec3 p1,vec3 p2,vec3 p3){
	vec3 r;
	
	vec3 u=p2-p1;
	vec3 v=p3-p1;
	
	r.x= (u.y*v.z) - (u.z*v.y);
	r.y= (u.z*v.x) - (u.x*v.z);
	r.z= (u.x*v.y) - (u.y*v.x);
	
	return r;
}


void vertex() {

	//ani+=TIME*waterspeed;
	//if(ani>0.5)
//		ani=-0.5;
	
	float thetexsize=1.0/1024.0;
	vec2 c=UV;
	//c.x+= ;
	
	//c.x+=();
	
	
	vec2 e=UV;
	e=c;
	e.x+=thetexsize;
	
	vec2 w=UV;
	w=c;
	w.x-=thetexsize;
	
	vec2 n=UV;
	n=c;
	n.y-=thetexsize;
	
	vec2 s=UV;
	s=c;
	s.y+=thetexsize;

	vec3 pc=VERTEX;
	vec3 pe=VERTEX;
	pe.x+=1.0;
	
	vec3 pw=VERTEX;
	pw.x-=1.0;
	
	vec3 pn=VERTEX;
	pn.z-=1.0;
	
	vec3 ps=VERTEX;
	ps.z+=1.0;

	pc.y=texture(noise,c).b*height_scale;
	pe.y=texture(noise,e).b*height_scale;
	pw.y=texture(noise,w).b*height_scale;
	pn.y=texture(noise,n).b*height_scale;
	ps.y=texture(noise,s).b*height_scale;
	
	vec3 sn=pn-ps;
	vec3 ew=pe-pw;
	
	vec3 nor=normalize(cross(ew,sn));
	
	NORMAL=nor;
	VERTEX=pc;
	
}

void fragment() {
	float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));	
	ALBEDO=watercol+(0.1 * fresnel);
	//ROUGHNESS=waterrough* (1.0-fresnel);
	ROUGHNESS=(fresnel*waterrough);
	METALLIC=watermet;
	RIM=waterrim;
	ALPHA=wateralpha;

}

Now I need to make it animate.

Shon answered 13/11, 2022 at 4:22 Comment(0)
T
0

That's awesome. I wouldn't have been able to figure that out.

Tonl answered 13/11, 2022 at 4:36 Comment(0)
F
0

Hedgerow : Now you can animate it in the shader by offsetting the y-values. Just calculate the normals after you have calculated the vertex positions.

And you can experiment with other methods of normal calculation, if you so wish.

Fitton answered 13/11, 2022 at 8:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.