@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
366 lines (289 loc) • 10.3 kB
JavaScript
import {
AddEquation,
CustomBlending,
GLSL3,
OneFactor,
OneMinusSrcAlphaFactor,
RawShaderMaterial,
Vector3
} from "three";
/*
*
* For ray projection using projection matrix : https://encreative.blogspot.com/2019/05/computing-ray-origin-and-direction-from.html
*/
const shader_vx = `
in vec2 uv;
in vec3 position;
in vec3 normal;
out vec2 vUv;
out vec4 plane0;
out vec4 plane1;
out vec4 plane2;
out vec3 local_ray_near;
out vec3 local_ray_far;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;
uniform vec3 uOffset;
uniform float uRadius;
uniform float uFrames;
void main() {
vUv = uv;
vec2 framesMinusOne = uFrames - vec2(1.0);
mat4 m4 = modelViewMatrix;
m4[0][0] = 1.0;
m4[0][1] = 0.0;
m4[0][2] = 0.0;
m4[1][0] = 0.0;
m4[1][1] = 1.0;
m4[1][2] = 0.0;
m4[2][0] = 0.0;
m4[2][1] = 0.0;
m4[2][2] = 1.0;
vec3 object_scale = vec3(
length(modelViewMatrix[0].xyz),
length(modelViewMatrix[1].xyz),
length(modelViewMatrix[2].xyz)
);
// scale by object's baking bounding sphere's radius
float card_diameter = uRadius*2.0;
object_scale *= card_diameter;
vec3 transformedNormal = normalize(normalMatrix * normal);
vec4 mvPosition = m4 * vec4( object_scale*(position+uOffset/card_diameter), 1.0 );
gl_Position = projectionMatrix * mvPosition;
mat4 inverse_matrix = inverse(projectionMatrix * modelViewMatrix);
//
//get 2D projection of this vertex in normalized device coordinates
vec2 pos = gl_Position.xy/gl_Position.w;
//compute ray's start and end as inversion of this coordinates
//in near and far clip planes
vec4 near_4 = inverse_matrix * (vec4(pos, -1.0, 1.0));
vec4 far_4 = inverse_matrix * (vec4(pos, 1.0, 1.0));
local_ray_near = near_4.xyz / near_4.w;
local_ray_far = far_4.xyz/ far_4.w;
}
`;
const shader_fg = `
precision highp float;
precision highp int;
const float depth_scale = 0.5;
in vec2 vUv;
in vec4 plane0;
in vec4 plane1;
in vec4 plane2;
in vec3 vViewPosition;
in vec3 vFacingDirection;
out vec4 color_out;
uniform sampler2D tBase;
uniform sampler2D tGeometry;
uniform float uFrames;
uniform float uDepthScale;
uniform bool uIsFullSphere;
in vec3 local_ray_near;
in vec3 local_ray_far;
struct Material{
vec3 diffuse;
vec3 normal;
float depth;
float occlusion;
float roughness;
float metalness;
};
vec2 VecToSphereOct(vec3 pivotToCamera)
{
vec3 octant = sign(pivotToCamera);
// |x| + |y| + |z| = 1
float sum = dot(pivotToCamera, octant);
vec3 octahedron = pivotToCamera / sum;
if (octahedron.y < 0.0){
vec3 absolute = abs(octahedron);
octahedron.xz = octant.xz * vec2(1.0 - absolute.z, 1.0 - absolute.x);
}
return octahedron.xz;
}
//for hemisphere
vec2 VecToHemiSphereOct(vec3 vec)
{
vec.y = max(vec.y, 0.001);
vec = normalize(vec);
vec3 octant = sign(vec);
// |x| + |y| + |z| = 1
float sum = dot(vec, octant);
vec3 octahedron = vec / sum;
return vec2(
octahedron.x + octahedron.z,
octahedron.z - octahedron.x
);
}
vec2 VectorToGrid(vec3 vec)
{
if (uIsFullSphere)
{
return VecToSphereOct(vec);
}
else
{
return VecToHemiSphereOct(vec);
}
}
vec4 TriangleInterpolate(vec2 uv){
uv = fract(uv);
vec2 omuv = vec2(1.0, 1.0) - uv.xy;
vec4 res = vec4(0, 0, 0, 0);
//frame 0
res.x = min(omuv.x, omuv.y);
//frame 1
res.y = abs(dot(uv, vec2(1.0, -1.0)));
//frame 2
res.z = min(uv.x, uv.y);
//mask
res.w = clamp(ceil(uv.x-uv.y),0.0, 1.0);
return res;
}
vec4 ImposterBlendWeights(sampler2D tex, vec2 frame0, vec2 frame1, vec2 frame2, vec4 weights, vec4 ddxy)
{
vec4 samp0 = textureGrad(tex, frame0, ddxy.xy, ddxy.zw);
vec4 samp1 = textureGrad(tex, frame1, ddxy.xy, ddxy.zw);
vec4 samp2 = textureGrad(tex, frame2, ddxy.xy, ddxy.zw);
vec4 result = samp0*weights.x + samp1*weights.y + samp2*weights.z;
return result;
}
vec4 ImposterBlendWeightsNearest(sampler2D tex, vec2 frame0, vec2 frame1, vec2 frame2, vec4 weights, vec4 ddxy)
{
vec4 samp0 = textureGrad(tex, frame0, ddxy.xy, ddxy.zw);
vec4 samp1 = textureGrad(tex, frame1, ddxy.xy, ddxy.zw);
vec4 samp2 = textureGrad(tex, frame2, ddxy.xy, ddxy.zw);
vec4 result;
if(weights.x > weights.y && weights.x > weights.z){
result = samp0;
}if(weights.y > weights.z){
result = samp1;
}else{
result = samp2;
}
return result;
}
void calcuateXYbasis(vec3 plane_normal, out vec3 plane_x, out vec3 plane_y)
{
vec3 up = vec3(0,1,0);
//cross product doesnt work if we look directly from bottom
if (abs(plane_normal.y) > 0.999f)
{
up = vec3(0,0,1);
}
plane_x = normalize(cross(plane_normal, up));
plane_y = normalize(cross(plane_x, plane_normal));
}
vec3 projectOnPlaneBasis(vec3 ray, vec3 plane_normal, vec3 plane_x, vec3 plane_y)
{
//reproject plane normal onto planeXY basos
return normalize(vec3(
dot(plane_x,ray),
dot(plane_y,ray),
dot(plane_normal,ray)
));
}
vec2 recompute_uv(vec2 frame, vec2 frame_uv, sampler2D gBuffer){
vec2 frame_size = vec2(1.0/ uFrames);
vec2 source_uv = (frame + frame_uv)*frame_size;
float n_depth = texture(gBuffer, source_uv).a;
vec2 offset = clamp(length(frame_uv*2.0 - 1.0) * vec2(0.5-n_depth ) * depth_scale,0.0, 1.0);
vec2 uv_f = clamp(frame_uv+offset, 0.0, 1.0);
uv_f = ( frame + uv_f)*frame_size;
return clamp(uv_f,0.0, 1.0);
// return source_uv;
}
void main(){
vec3 view_direction = normalize(local_ray_near-local_ray_far);
vec2 octahedral_uv = clamp(VectorToGrid(view_direction)*0.5 + 0.5, 0.0, 1.0);
vec2 grid = octahedral_uv * vec2(uFrames - 1.0);
vec2 gridFrac = fract(grid);
vec2 gridFloor = floor(grid);
vec4 weights = TriangleInterpolate( gridFrac );
vec2 frame_uv = vUv;
//3 nearest frames
vec2 frame0 = gridFloor;
vec2 frame1 = gridFloor + mix(vec2(0,1),vec2(1,0),weights.w);
vec2 frame2 = gridFloor + vec2(1.0,1.0);
vec2 uv0 = recompute_uv(frame0, frame_uv, tGeometry);
vec2 uv1 = recompute_uv(frame1, frame_uv, tGeometry);
vec2 uv2 = recompute_uv(frame2, frame_uv, tGeometry);
vec4 ddxy = vec4( dFdx(vUv.xy), dFdy(vUv.xy) );
vec2 frame_size = vec2(1.0/ uFrames);
vec4 texel_color = ImposterBlendWeights(
// vec4 texel_color = ImposterBlendWeightsNearest(
tBase,
uv0,
uv1,
uv2,
weights, ddxy
);
// texel_color = vec4(texel_color.aaa, 1.0);
if(texel_color.a <= 0.5){
texel_color.r = 1.0;
discard;
}
color_out = texel_color;
// color_out = vec4( snapped_oct_uv, 1.0, 1.0);
}
`;
export class ImpostorShaderV0 extends RawShaderMaterial {
constructor() {
super({
fragmentShader: shader_fg,
vertexShader: shader_vx,
uniforms: {
/**
* RGB + Alpha
*/
tBase: {
value: null
},
/**
* Normal+Depth
*/
tGeometry: {
value: null
},
/**
* Material properties: Occlusion, Roughness, Metalness
* Alpha unused
*/
tMaterial: {
value: null
},
/**
* Number of frames
*/
uFrames: {
value: 0
},
/**
* Radius of bounding sphere of the impostor
*/
uRadius: {
value: 0
},
/**
* Impostor offset
*/
uOffset: {
value: new Vector3(0, 0, 0)
},
uIsFullSphere: {
value: false
},
uDepthScale:{
// value should be in range between 0 and 1
value:1
}
},
glslVersion: GLSL3
});
// Save some effort by disabling blending
this.blending = CustomBlending;
this.blendEquation = AddEquation;
this.blendSrc = OneFactor;
this.blendDst = OneMinusSrcAlphaFactor;
}
}