shader_type spatial; render_mode blend_mix, depth_prepass_alpha, cull_back, unshaded; uniform vec4 albedo : source_color = vec4(1, 1, 1, 1); uniform float specular = 0.5f; uniform float metallic = 0.0f; uniform float roughness : hint_range(0, 1) = 1.0f; uniform sampler2D imposterTextureAlbedo : source_color; uniform vec2 imposterFrames = vec2(16.0f, 16.0f); uniform vec3 positionOffset = vec3(0.0f); uniform bool isFullSphere = true; uniform float alpha_clamp = 0.5f; uniform float scale = 1.0f; uniform float depth_scale : hint_range(0, 1) = 0.0f; uniform float aabb_max = 1.0; varying vec2 uv_frame1; varying vec2 xy_frame1; varying flat vec2 frame1; varying vec4 quad_blend_weights; 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.0f) { vec3 absolute = abs(octahedron); octahedron.xz = octant.xz * vec2(1.0f - absolute.z, 1.0f - absolute.x); } return octahedron.xz; } vec2 VecToHemiSphereOct(vec3 pivotToCamera) { pivotToCamera.y = max(pivotToCamera.y, 0.001); pivotToCamera = normalize(pivotToCamera); vec3 octant = sign(pivotToCamera); // |x| + |y| + |z| = 1 float sum = dot(pivotToCamera, octant); vec3 octahedron = pivotToCamera / sum; return vec2( octahedron.x + octahedron.z, octahedron.z - octahedron.x); } vec2 VectorToGrid(vec3 vec) { if (isFullSphere) { return VecToSphereOct(vec); } else { return VecToHemiSphereOct(vec); } } //for sphere vec3 OctaSphereEnc(vec2 coord) { coord = (coord - 0.5) * 2.0; vec3 position = vec3(coord.x, 0.0f, coord.y); vec2 absolute = abs(position.xz); position.y = 1.0f - absolute.x - absolute.y; if (position.y < 0.0f) { position.xz = sign(position.xz) * vec2(1.0f - absolute.y, 1.0f - absolute.x); } return position; } //for hemisphere vec3 OctaHemiSphereEnc(vec2 coord) { vec3 position = vec3(coord.x - coord.y, 0.0f, -1.0 + coord.x + coord.y); vec2 absolute = abs(position.xz); position.y = 1.0f - absolute.x - absolute.y; return position; } vec3 GridToVector(vec2 coord) { if (isFullSphere) { return OctaSphereEnc(coord); } else { return OctaHemiSphereEnc(coord); } } vec3 FrameXYToRay(vec2 frame, vec2 frameCountMinusOne) { //divide frame x y by framecount minus one to get 0-1 vec2 f = (frame.xy/ frameCountMinusOne); //bias and scale to -1 to 1 vec3 vec = GridToVector(f); vec = normalize(vec); return vec; } vec3 SpriteProjection(vec3 pivotToCameraRayLocal, vec2 size, vec2 loc_uv) { vec3 z = normalize(pivotToCameraRayLocal); vec3 x, y; vec3 up = vec3(0,1,0); //cross product doesnt work if we look directly from bottom if (abs(z.y) > 0.999f) { up = vec3(0,0,-1); } x = normalize(cross(up, z)); y = normalize(cross(x, z)); loc_uv -= vec2(0.5,0.5); vec2 uv = (loc_uv) * 2.0; //-1 to 1 vec3 newX = x * uv.x; vec3 newY = y * uv.y; vec2 vecSize = size * 0.5; newX *= vecSize.x; newY *= vecSize.y; return newX + newY; } vec4 quadBlendWieghts(vec2 coords) { vec4 res; /* 0 0 0 0 0 0 1 0 0 */ res.x = min(1.0f - coords.x, 1.0f - coords.y); /* 1 0 0 0 0 0 0 0 1 */ res.y = abs(coords.x - coords.y); /* 0 0 1 0 0 0 0 0 0 */ res.z = min(coords.x, coords.y); /* 0 0 0 0 0 1 0 1 1 */ res.w = ceil(coords.x - coords.y); //res.xyz /= (res.x + res.y + res.z); return res; } //this function works well in orthogonal projection. It works okeyish with further distances of perspective projection vec2 virtualPlaneUV(vec3 plane_normal,vec3 plane_x, vec3 plane_y, vec3 pivotToCameraRay, vec3 vertexToCameraRay, float size) { plane_normal = normalize(plane_normal); plane_x = normalize(plane_x); plane_y = normalize(plane_y); float projectedNormalRayLength = dot(plane_normal, pivotToCameraRay); float projectedVertexRayLength = dot(plane_normal, vertexToCameraRay); float offsetLength = projectedNormalRayLength/projectedVertexRayLength; vec3 offsetVector = vertexToCameraRay * offsetLength - pivotToCameraRay; vec2 duv = vec2( dot(plane_x , offsetVector), dot(plane_y, offsetVector) ); //we are in space -1 to 1 duv /= 2.0 * size; duv += 0.5; return duv; } 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) )); } void vertex() { vec2 framesMinusOne = imposterFrames - vec2(1); vec3 cameraPos_WS = (INV_VIEW_MATRIX * vec4(vec3(0), 1.0)).xyz; vec3 cameraPos_OS = (inverse(MODEL_MATRIX) * vec4(cameraPos_WS, 1.0)).xyz; //TODO: check if this is correct. We are using orho projected images, so // camera far away vec3 pivotToCameraRay = (cameraPos_OS) * 10.0; vec3 pivotToCameraDir = normalize(cameraPos_OS); vec2 grid = VectorToGrid(pivotToCameraDir); //bias and scale to 0 to 1 grid = clamp((grid + 1.0) * 0.5, vec2(0, 0), vec2(1, 1)); grid *= framesMinusOne; grid = clamp(grid, vec2(0), vec2(framesMinusOne)); vec2 gridFloor = min(floor(grid), framesMinusOne); vec2 gridFract = fract(grid); //radius * 2 vec2 size = vec2(2.0) * scale; vec3 projected = SpriteProjection(pivotToCameraDir, size, UV); vec3 vertexToCameraRay = (pivotToCameraRay - (projected)); vec3 vertexToCameraDir = normalize(vertexToCameraRay); frame1 = gridFloor; quad_blend_weights = quadBlendWieghts(gridFract); //convert frame coordinate to octahedron direction vec3 projectedQuadADir = FrameXYToRay(frame1, framesMinusOne); //calcute virtual planes projections vec3 plane_x1, plane_y1; calcuateXYbasis(projectedQuadADir, plane_x1, plane_y1); uv_frame1 = virtualPlaneUV(projectedQuadADir, plane_x1, plane_y1, pivotToCameraRay, vertexToCameraRay, scale); xy_frame1 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadADir, plane_x1, plane_y1).xy; //to fragment shader VERTEX.xyz = projected + positionOffset; VERTEX.xyz +=pivotToCameraDir* aabb_max; vec3 v0 = abs(NORMAL.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, 0.0, 1.0); NORMAL = normalize(pivotToCameraDir); TANGENT= normalize(cross(NORMAL,v0)); BINORMAL = normalize(cross(TANGENT,NORMAL)); } vec2 recalculateUV(vec2 uv_f, vec2 frame, vec2 xy_f, vec2 frame_size) { //clamp for parallax sampling uv_f = clamp(uv_f, vec2(0), vec2(1)); return frame_size * (frame + uv_f); } void fragment() { vec2 quad_size = vec2(1.0f) / imposterFrames; vec2 uv_f1 = recalculateUV(uv_frame1, frame1, xy_frame1, quad_size); vec4 baseTex = textureLod(imposterTextureAlbedo, uv_f1, 0.0f); ALBEDO = baseTex.rgb; ALPHA = float(baseTex.a>alpha_clamp); ALPHA_SCISSOR_THRESHOLD = 0.5; ROUGHNESS = 1.0; }