@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
129 lines (90 loc) • 4.22 kB
JavaScript
import { Vector2 } from "three";
const AmbientOcclusionShader = function () {
return {
uniforms: {
"normalMap": { type: "t", value: null },
"heightMap": { type: "t", value: null },
"world_size": { type: "v2", value: new Vector2(512, 512) },
"rayLength": { type: 'f', value: 17 }
},
defines: {
'NUM_SAMPLES': 64,
'NUM_RINGS': 7,
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("\n"),
fragmentShader: [
`
uniform sampler2D normalMap;
uniform sampler2D heightMap;
uniform float rayLength;
uniform vec2 world_size;
varying vec2 vUv;
vec3 get(float x, float y){
vec2 _uv = vUv.xy + vec2(x,y) / world_size;
float h = texture2D(heightMap, _uv).x;
return vec3( _uv.x * world_size.x, h, _uv.y * world_size.y );
}
float hash1( float n )
{
return fract( n*17.0*fract( n*0.3183099 ) );
}
float hash1( vec2 p )
{
p = 50.0*fract( p*0.3183099 );
return fract( p.x*p.y*(p.x+p.y) );
}
// Non-sin based hash function, fast and has good randomness
float hash12(vec2 p){
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
const float bias = 0.001;
float pow2(float x){
return x*x;
}
float getOcclusion(vec3 origin, vec3 normal, vec3 hit_position){
vec3 viewDelta = hit_position - origin;
float viewDistance = length( viewDelta );
float vn = dot( normal, viewDelta );
float a2 = (vn) / viewDistance - bias;
float a1 = (1.0 + pow2( viewDistance ) );
return max(0.0, a2) / a1;
}
const float PI2 = 6.28318530717958;
const float kernelRadius = 100.0;
const float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES );
const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES );
float getAmbientOcclusion(vec3 world_position, vec3 world_normal){
// jsfiddle that shows sample pattern: https://jsfiddle.net/a16ff1p7/
float angle = hash12( vUv ) * PI2;
vec2 radius = vec2( rayLength * INV_NUM_SAMPLES );
float occlusionSum = 0.0;
float weightSum = 0.0;
for( int i = 0; i < NUM_SAMPLES; i ++ ) {
vec2 sampleUv = vec2( cos( angle ), sin( angle ) ) * radius * float(i+1);
angle += ANGLE_STEP;
vec3 sample_pos = get(sampleUv.x, sampleUv.y);
occlusionSum += getOcclusion(world_position, world_normal, sample_pos);
weightSum += 1.0;
}
return occlusionSum/weightSum;
}
void main() {
vec3 pos = get(0.0, 0.0);
vec3 normal = texture2D( normalMap, vUv ).xzy;
float occlusion = getAmbientOcclusion(pos, normal);
float incident = 1.0 - occlusion;
gl_FragColor = vec4(pow(incident,10.0), 0.0, 0.0, 1.0);
}
`
].join('\n')
}
};
export default AmbientOcclusionShader;