@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
220 lines (193 loc) • 6.8 kB
JavaScript
import {
AddEquation,
CustomBlending,
GLSL3,
OneFactor,
OneMinusSrcAlphaFactor,
RawShaderMaterial,
Vector3
} from "three";
// The wireframe must trace the SAME card geometry that ImpostorShaderV0
// produces, otherwise the red outline lies about where the impostor surface
// actually is. So this vertex shader is the geometry half of the main V0
// vertex shader, minus everything the fragment shader doesn't read (no
// vUv, vViewPos, vTangent/vBinormal/vNormal, no per-frame xforms, no
// atlas lookup).
const shader_vx = `
in vec3 position;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec3 uOffset;
uniform float uRadius;
uniform float uFrames;
uniform bool uIsFullSphere;
// ----- direction -> octahedral grid coord (range -1..+1) -----
vec2 VecToSphereOct(vec3 pivotToCamera)
{
vec3 octant = sign(pivotToCamera);
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;
}
vec2 VecToHemiSphereOct(vec3 v)
{
v.y = max(v.y, 0.001);
v = normalize(v);
vec3 octant = sign(v);
float sum = dot(v, octant);
vec3 octahedron = v / sum;
return vec2(
octahedron.x + octahedron.z,
octahedron.z - octahedron.x
);
}
vec2 VectorToGrid(vec3 v)
{
return uIsFullSphere ? VecToSphereOct(v) : VecToHemiSphereOct(v);
}
// ----- octahedral grid coord (range 0..1) -> direction -----
vec3 OctaSphereDec(vec2 coord)
{
coord = (coord - 0.5) * 2.0;
vec3 p = vec3(coord.x, 0.0, coord.y);
vec2 a = abs(p.xz);
p.y = 1.0 - a.x - a.y;
if (p.y < 0.0) {
p.xz = sign(p.xz) * vec2(1.0 - a.y, 1.0 - a.x);
}
return p;
}
vec3 OctaHemiSphereDec(vec2 coord)
{
vec3 p = vec3(coord.x - coord.y, 0.0, -1.0 + coord.x + coord.y);
vec2 a = abs(p.xz);
p.y = 1.0 - a.x - a.y;
return p;
}
vec3 GridToVector(vec2 coord)
{
return uIsFullSphere ? OctaSphereDec(coord) : OctaHemiSphereDec(coord);
}
vec3 FrameToRay(vec2 frame, vec2 framesMinusOne)
{
vec2 f = clamp(frame / framesMinusOne, 0.0, 1.0);
return normalize(GridToVector(f));
}
vec4 BilinearWeights(vec2 frac_uv)
{
vec2 omuv = vec2(1.0) - frac_uv;
return vec4(
omuv.x * omuv.y,
frac_uv.x * omuv.y,
omuv.x * frac_uv.y,
frac_uv.x * frac_uv.y
);
}
void main() {
// Pivot-to-camera direction in object-local space; same as V0.
vec3 cameraPos_OS = (inverse(modelViewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
vec3 pivotToCameraRay = normalize(cameraPos_OS);
// Pick the cell and its 4-corner bilinear weights, then blend the
// four corner rays into the "effective" bake direction the card is
// showing. Mirrors V0.
vec2 framesMinusOne = vec2(uFrames - 1.0);
vec2 octahedral_uv = clamp(VectorToGrid(pivotToCameraRay) * 0.5 + 0.5, 0.0, 1.0);
vec2 grid = octahedral_uv * framesMinusOne;
vec2 gridFloor = min(floor(grid), framesMinusOne - 1.0);
vec4 weights = BilinearWeights(grid - gridFloor);
vec3 ray00 = FrameToRay(gridFloor + vec2(0.0, 0.0), framesMinusOne);
vec3 ray10 = FrameToRay(gridFloor + vec2(1.0, 0.0), framesMinusOne);
vec3 ray01 = FrameToRay(gridFloor + vec2(0.0, 1.0), framesMinusOne);
vec3 ray11 = FrameToRay(gridFloor + vec2(1.0, 1.0), framesMinusOne);
vec3 projectedRay = normalize(
ray00 * weights.x +
ray10 * weights.y +
ray01 * weights.z +
ray11 * weights.w
);
// TBN construction identical to V0 so the card spans the same plane.
vec3 normal_OS = projectedRay;
vec3 up_OS = abs(normal_OS.y) > 0.999
? vec3(0.0, 0.0, -1.0)
: vec3(0.0, 1.0, 0.0);
vec3 tangent_OS = normalize(cross(up_OS, normal_OS));
vec3 binormal_OS = cross(normal_OS, tangent_OS);
float card_diameter = uRadius * 2.0;
vec3 pos_OS = uOffset
+ position.x * card_diameter * tangent_OS
+ position.y * card_diameter * binormal_OS;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos_OS, 1.0);
}
`;
const shader_fg = `
precision highp float;
precision highp int;
out vec4 color_out;
void main(){
color_out = vec4(1.0, .0, .0, 1.0);
}
`;
export class ImpostorShaderWireframeV0 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;
}
}