@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
307 lines (245 loc) • 11.5 kB
JavaScript
import { GLSL3, NoBlending, RawShaderMaterial, Vector3 } from "three";
import chunk_preamble from './glsl/common.glsl';
const shader_vx = `
in vec2 uv;
in vec3 position;
out vec2 vUv;
out vec4 plane0;
out vec4 plane1;
out vec4 plane2;
${chunk_preamble}
void ImposterVertex( inout ImposterData imp )
{
//incoming vertex, object space
vec4 vertex = imp.vertex;
//camera in object space
vec3 objectSpaceCameraPos = mul( unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz,1) ).xyz;
vec2 texcoord = imp.uv;
float4x4 objectToWorld = unity_ObjectToWorld;
float4x4 worldToObject = unity_WorldToObject;
vec3 imposterPivotOffset = _ImposterOffset.xyz;
half framesMinusOne = _ImposterFrames-1;
float3 objectScale = float3(length(float3(objectToWorld[0].x, objectToWorld[1].x, objectToWorld[2].x)),
length(float3(objectToWorld[0].y, objectToWorld[1].y, objectToWorld[2].y)),
length(float3(objectToWorld[0].z, objectToWorld[1].z, objectToWorld[2].z)));
//pivot to camera ray
float3 pivotToCameraRay = normalize(objectSpaceCameraPos.xyz-imposterPivotOffset.xyz);
//scale uv to single frame
texcoord = vec2(texcoord.x,texcoord.y)*(1.0/_ImposterFrames.x);
//radius * 2 * unity scaling
vec2 size = _ImposterSize.xx * 2.0; // * objectScale.xx; //unity_BillboardSize.xy
vec3 projected = SpriteProjection( pivotToCameraRay, _ImposterFrames, size, texcoord.xy );
//this creates the proper offset for vertices to camera facing billboard
vec3 vertexOffset = projected + imposterPivotOffset;
//subtract from camera pos
vertexOffset = normalize(objectSpaceCameraPos-vertexOffset);
//then add the original projected world
vertexOffset += projected;
//remove position of vertex
vertexOffset -= vertex.xyz;
//add pivot
vertexOffset += imposterPivotOffset;
//camera to projection vector
vec3 rayDirectionLocal = (imposterPivotOffset + projected) - objectSpaceCameraPos;
//projected position to camera ray
vec3 projInterpolated = normalize( objectSpaceCameraPos - (projected + imposterPivotOffset) );
Ray rayLocal;
rayLocal.Origin = objectSpaceCameraPos-imposterPivotOffset;
rayLocal.Direction = rayDirectionLocal;
vec2 grid = VectorToGrid( pivotToCameraRay );
vec2 gridRaw = grid;
grid = saturate((grid+1.0)*0.5); //bias and scale to 0 to 1
grid *= framesMinusOne;
vec2 gridFrac = frac(grid);
vec2 gridFloor = floor(grid);
vec4 weights = TriangleInterpolate( gridFrac );
//3 nearest frames
vec2 frame0 = gridFloor;
vec2 frame1 = gridFloor + lerp(vec2(0,1),vec2(1,0),weights.w);
vec2 frame2 = gridFloor + vec2(1,1);
//convert frame coordinate to octahedron direction
vec3 frame0ray = FrameXYToRay(frame0, framesMinusOne.xx);
vec3 frame1ray = FrameXYToRay(frame1, framesMinusOne.xx);
vec3 frame2ray = FrameXYToRay(frame2, framesMinusOne.xx);
vec3 planeCenter = vec3(0,0,0);
vec3 plane0x;
vec3 plane0normal = frame0ray;
vec3 plane0z;
vec3 frame0local = FrameTransform( projInterpolated, frame0ray, plane0x, plane0z );
frame0local.xz = frame0local.xz/_ImposterFrames.xx; //for displacement
//virtual plane UV coordinates
vec2 vUv0 = VirtualPlaneUV( plane0normal, plane0x, plane0z, planeCenter, size, rayLocal );
vUv0 /= _ImposterFrames.xx;
vec3 plane1x;
vec3 plane1normal = frame1ray;
vec3 plane1z;
vec3 frame1local = FrameTransform( projInterpolated, frame1ray, plane1x, plane1z);
frame1local.xz = frame1local.xz/_ImposterFrames.xx; //for displacement
//virtual plane UV coordinates
vec2 vUv1 = VirtualPlaneUV( plane1normal, plane1x, plane1z, planeCenter, size, rayLocal );
vUv1 /= _ImposterFrames.xx;
vec3 plane2x;
vec3 plane2normal = frame2ray;
vec3 plane2z;
vec3 frame2local = FrameTransform( projInterpolated, frame2ray, plane2x, plane2z );
frame2local.xz = frame2local.xz/_ImposterFrames.xx; //for displacement
//virtual plane UV coordinates
vec2 vUv2 = VirtualPlaneUV( plane2normal, plane2x, plane2z, planeCenter, size, rayLocal );
vUv2 /= _ImposterFrames.xx;
//add offset here
imp.vertex.xyz += vertexOffset;
//overwrite others
imp.uv = texcoord;
imp.grid = grid;
imp.frame0 = vec4(vUv0.xy,frame0local.xz);
imp.frame1 = vec4(vUv1.xy,frame1local.xz);
imp.frame2 = vec4(vUv2.xy,frame2local.xz);
}
void main() {
vUv = uv;
ImposterData imp;
imp.vertex = vec4(position, 1.0);
imp.uv = uv;
ImposterVertex(imp);
gl_Position = imp.vertex;
float3 normalWorld = UnityObjectToWorldDir(v.normal.xyz);
float3 tangentWorld = UnityObjectToWorldDir(v.tangent.xyz);
float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld, v.tangent.w);
o.tangentWorld = tangentToWorld[0];
o.bitangentWorld = tangentToWorld[1];
o.normalWorld = tangentToWorld[2];
//surface
o.texCoord.xy = imp.uv;
o.texCoord.zw = imp.grid;
o.plane0 = imp.frame0;
o.plane1 = imp.frame1;
o.plane2 = imp.frame2;
}
`;
const shader_fg = `
in vec2 vUv;
in vec4 plane0;
in vec4 plane1;
in vec4 plane2;
struct Material{
vec3 diffuse;
vec3 normal;
float depth;
float occlusion;
float roughness;
float metalness;
};
void ImposterSample( in ImposterData imp, out vec4 baseTex, out vec4 worldNormal )//, out half depth )
{
vec2 fracGrid = frac(imp.grid);
vec4 weights = TriangleInterpolate( fracGrid );
vec2 gridSnap = floor(imp.grid) / _ImposterFrames.xx;
vec2 frame0 = gridSnap;
vec2 frame1 = gridSnap + (lerp(vec2(0,1),vec2(1,0),weights.w)/_ImposterFrames.xx);
vec2 frame2 = gridSnap + (vec2(1,1)/_ImposterFrames.xx);
vec2 vp0uv = frame0 + imp.frame0.xy;
vec2 vp1uv = frame1 + imp.frame1.xy;
vec2 vp2uv = frame2 + imp.frame2.xy;
//resolution of atlas (Square)
float textureDims = _ImposterBaseTex_TexelSize.z;
//fractional frame size, ex 2048/12 = 170.6
float frameSize = textureDims/_ImposterFrames;
//actual atlas resolution used, ex 170*12 = 2040
float actualDims = floor(frameSize) * _ImposterFrames;
//the scale factor to apply to UV coordinate, ex 2048/2040 = 0.99609375
float scaleFactor = actualDims / textureDims;
vp0uv *= scaleFactor;
vp1uv *= scaleFactor;
vp2uv *= scaleFactor;
//clamp out neighboring frames TODO maybe discard instead?
vec2 gridSize = 1.0/_ImposterFrames.xx;
gridSize *= _ImposterBaseTex_TexelSize.zw;
gridSize *= _ImposterBaseTex_TexelSize.xy;
float2 border = _ImposterBaseTex_TexelSize.xy*_ImposterBorderClamp;
//vp0uv = clamp(vp0uv,frame0+border,frame0+gridSize-border);
//vp1uv = clamp(vp1uv,frame1+border,frame1+gridSize-border);
//vp2uv = clamp(vp2uv,frame2+border,frame2+gridSize-border);
//for parallax modify
vec4 n0 = tex2Dlod( _ImposterWorldNormalDepthTex, vec4(vp0uv, 0, 1 ) );
vec4 n1 = tex2Dlod( _ImposterWorldNormalDepthTex, vec4(vp1uv, 0, 1 ) );
vec4 n2 = tex2Dlod( _ImposterWorldNormalDepthTex, vec4(vp2uv, 0, 1 ) );
half n0s = 0.5-n0.a;
half n1s = 0.5-n1.a;
half n2s = 0.5-n2.a;
vec2 n0p = imp.frame0.zw * n0s;
vec2 n1p = imp.frame1.zw * n1s;
vec2 n2p = imp.frame2.zw * n2s;
//add parallax shift
vp0uv += n0p;
vp1uv += n1p;
vp2uv += n2p;
//clamp out neighboring frames TODO maybe discard instead?
vp0uv = clamp(vp0uv,frame0+border,frame0+gridSize-border);
vp1uv = clamp(vp1uv,frame1+border,frame1+gridSize-border);
vp2uv = clamp(vp2uv,frame2+border,frame2+gridSize-border);
vec2 ddxy = vec2( ddx(imp.uv.x), ddy(imp.uv.y) );
worldNormal = ImposterBlendWeights( _ImposterWorldNormalDepthTex, imp.uv, vp0uv, vp1uv, vp2uv, weights, ddxy );
baseTex = ImposterBlendWeights( _ImposterBaseTex, imp.uv, vp0uv, vp1uv, vp2uv, weights, ddxy );
//pixel depth offset
//half pdo = 1-baseTex.a;
//float3 objectScale = float3(length(float3(unity_ObjectToWorld[0].x, unity_ObjectToWorld[1].x, unity_ObjectToWorld[2].x)),
// length(float3(unity_ObjectToWorld[0].y, unity_ObjectToWorld[1].y, unity_ObjectToWorld[2].y)),
// length(float3(unity_ObjectToWorld[0].z, unity_ObjectToWorld[1].z, unity_ObjectToWorld[2].z)));
//vec2 size = _ImposterSize.xx * 2.0;// * objectScale.xx;
//vec3 viewWorld = mul( UNITY_MATRIX_VP, float4(0,0,1,0) ).xyz;
//pdo *= size * abs(dot(normalize(imp.viewDirWorld.xyz),viewWorld));
//depth = pdo;
}
void main(){
}
`;
export class ImpostorShaderStandard 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)
}
},
glslVersion: GLSL3
});
// Save some effort by disabling blending
this.blending = NoBlending;
}
}