Last active
March 20, 2022 12:50
-
-
Save Beherith/107f3a31371f6cbe78e62833a84fed3a to your computer and use it in GitHub Desktop.
better outlines
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function widget:GetInfo() | |
return { | |
name = "Unit Outlines GL4", | |
desc = "An interesting way of doing unit outlines", | |
author = "Beherith", | |
date = "2022.03.05", | |
license = "Lua: GNU GPL, v2 or later, GLSL code: (c) Beherith, mysterme@gmail.com ", | |
layer = -50, | |
enabled = false | |
} | |
end | |
local myvisibleUnits = {} -- table of unitID : unitDefID | |
local resurrectionHalosVBO = nil | |
local resurrectionHalosShader = nil | |
local luaShaderDir = "LuaUI/Widgets/Include/" | |
local texture = 'LuaUI/Images/halo.dds' | |
local OPTIONS = { | |
haloSize = 0.8, | |
haloDistance = 2.0, | |
skipBuildings = true, | |
} | |
local unitConf = {} | |
for unitDefID, unitDef in pairs(UnitDefs) do | |
--if not OPTIONS.skipBuildings or (OPTIONS.skipBuildings and not (unitDef.isBuilding or unitDef.isFactory or unitDef.speed==0)) then | |
local xsize, zsize = unitDef.xsize, unitDef.zsize | |
local scale = math.max(xsize,zsize) | |
unitConf[unitDefID] = {scale=scale, iconSize=scale*OPTIONS.haloSize, height=unitDef.height} | |
--end | |
end | |
local shaderConfig = { | |
TRANSPARENCY = 0.2, -- transparency of the stuff drawn | |
HEIGHTOFFSET = 1, -- Additional height added to everything | |
ANIMATION = 1, -- set to 0 if you dont want animation | |
INITIALSIZE = 0.66, -- What size the stuff starts off at when spawned | |
GROWTHRATE = 4, -- How fast it grows to full size | |
BREATHERATE = 30.0, -- how fast it periodicly grows | |
BREATHESIZE = 0.05, -- how much it periodicly grows | |
TEAMCOLORIZATION = 1.0, -- not used yet | |
CLIPTOLERANCE = 1.1, -- At 1.0 it wont draw at units just outside of view (may pop in), 1.1 is a good safe amount | |
USETEXTURE = 1, -- 1 if you want to use textures (atlasses too!) , 0 if not | |
BILLBOARD = 1, -- 1 if you want camera facing billboards, 0 is flat on ground | |
POST_ANIM = " ", -- what you want to do in the animation post function (glsl snippet, see shader source) | |
POST_VERTEX = "v_color = v_color;", -- noop | |
POST_GEOMETRY = "gl_Position.z = (gl_Position.z) - 256.0 / (gl_Position.w);", --"g_uv.zw = dataIn[0].v_parameters.xy;", -- noop | |
POST_SHADING = "fragColor.rgba = fragColor.rgba;", -- noop | |
MAXVERTICES = 64, -- The max number of vertices we can emit, make sure this is consistent with what you are trying to draw (tris 3, quads 4, corneredrect 8, circle 64 | |
USE_CIRCLES = 1, -- set to nil if you dont want circles | |
USE_CORNERRECT = 1, -- set to nil if you dont want cornerrect | |
USE_TRIANGLES = 1, | |
FULL_ROTATION = 0, -- the primitive is fully rotated in the units plane | |
DISCARD = 0, -- Enable alpha threshold to discard fragments below 0.01 | |
} | |
---- GL4 Backend Stuff---- | |
local DrawPrimitiveAtUnitVBO = nil | |
local DrawPrimitiveAtUnitShader = nil | |
local luaShaderDir = "LuaUI/Widgets/Include/" | |
local LuaShader = VFS.Include(luaShaderDir.."LuaShader.lua") | |
VFS.Include(luaShaderDir.."instancevbotable.lua") | |
local vsSrc = [[ | |
#version 420 | |
#extension GL_ARB_uniform_buffer_object : require | |
#extension GL_ARB_shader_storage_buffer_object : require | |
#extension GL_ARB_shading_language_420pack: require | |
#line 5000 | |
layout (location = 0) in vec4 lengthwidthcornerheight; | |
layout (location = 1) in uint teamID; | |
layout (location = 2) in uint numvertices; | |
layout (location = 3) in vec4 parameters; // lifestart, ismine | |
layout (location = 4) in vec4 uvoffsets; // this is optional, for using an Atlas | |
layout (location = 5) in uvec4 instData; | |
//__ENGINEUNIFORMBUFFERDEFS__ | |
//__DEFINES__ | |
struct SUniformsBuffer { | |
uint composite; // u8 drawFlag; u8 unused1; u16 id; | |
uint unused2; | |
uint unused3; | |
uint unused4; | |
float maxHealth; | |
float health; | |
float unused5; | |
float unused6; | |
vec4 speed; | |
vec4[5] userDefined; //can't use float[20] because float in arrays occupies 4 * float space | |
}; | |
layout(std140, binding=1) readonly buffer UniformsBuffer { | |
SUniformsBuffer uni[]; | |
}; | |
#line 10000 | |
uniform float addRadius; | |
uniform float iconDistance; | |
out DataVS { | |
uint v_numvertices; | |
float v_rotationY; | |
vec4 v_color; | |
vec4 v_lengthwidthcornerheight; | |
vec4 v_centerpos; | |
vec4 v_uvoffsets; | |
vec4 v_parameters; | |
}; | |
layout(std140, binding=0) readonly buffer MatrixBuffer { | |
mat4 UnitPieces[]; | |
}; | |
bool vertexClipped(vec4 clipspace, float tolerance) { | |
return any(lessThan(clipspace.xyz, -clipspace.www * tolerance)) || | |
any(greaterThan(clipspace.xyz, clipspace.www * tolerance)); | |
} | |
void main() | |
{ | |
uint baseIndex = instData.x; // this tells us which unit matrix to find | |
mat4 modelMatrix = UnitPieces[baseIndex]; // This gives us the models world pos and rot matrix | |
gl_Position = cameraViewProj * vec4(modelMatrix[3].xyz, 1.0); // We transform this vertex into the center of the model | |
v_rotationY = atan(modelMatrix[0][2], modelMatrix[0][0]); // we can get the euler Y rot of the model from the model matrix | |
v_uvoffsets = uvoffsets; | |
v_parameters = parameters; | |
v_color = teamColor[teamID]; // We can lookup the teamcolor right here | |
v_centerpos = vec4( modelMatrix[3].xyz, 1.0); // We are going to pass the centerpoint to the GS | |
v_lengthwidthcornerheight = lengthwidthcornerheight; | |
v_numvertices = numvertices; | |
if (vertexClipped(gl_Position, CLIPTOLERANCE)) v_numvertices = 0; // Make no primitives on stuff outside of screen | |
// TODO: take into account size of primitive before clipping | |
// this sets the num prims to 0 for units further from cam than iconDistance | |
float cameraDistance = length((cameraViewInv[3]).xyz - v_centerpos.xyz); | |
if (cameraDistance > iconDistance) v_numvertices = 0; | |
if (dot(v_centerpos.xyz, v_centerpos.xyz) < 1.0) v_numvertices = 0; // if the center pos is at (0,0,0) then we probably dont have the matrix yet for this unit, because it entered LOS but has not been drawn yet. | |
v_centerpos.y += HEIGHTOFFSET; // Add some height to ensure above groundness | |
v_centerpos.y += lengthwidthcornerheight.w; // Add per-instance height offset | |
if ((uni[instData.y].composite & 0x00000003u) < 1u ) v_numvertices = 0u; // this checks the drawFlag of wether the unit is actually being drawn (this is ==1 when then unit is both visible and drawn as a full model (not icon)) | |
// TODO: allow overriding this check, to draw things even if unit (like a building) is not drawn | |
POST_VERTEX | |
} | |
]] | |
local gsSrc = [[ | |
#version 330 | |
#extension GL_ARB_uniform_buffer_object : require | |
#extension GL_ARB_shading_language_420pack: require | |
//__ENGINEUNIFORMBUFFERDEFS__ | |
//__DEFINES__ | |
layout(points) in; | |
layout(triangle_strip, max_vertices = 4) out; | |
#line 20000 | |
uniform float addRadius; | |
uniform float iconDistance; | |
in DataVS { | |
uint v_numvertices; | |
float v_rotationY; | |
vec4 v_color; | |
vec4 v_lengthwidthcornerheight; | |
vec4 v_centerpos; | |
vec4 v_uvoffsets; | |
vec4 v_parameters; | |
} dataIn[]; | |
out DataGS { | |
vec4 g_color; | |
vec4 g_uv; | |
float camdistance; | |
}; | |
mat3 rotY; | |
vec4 centerpos; | |
void offsetVertex4( float x, float y, float z, float u, float v){ | |
g_uv.xy = vec2(u,v); | |
vec3 primitiveCoords = vec3(x,y,z); | |
vec3 vecnorm = normalize(primitiveCoords); | |
gl_Position = cameraViewProj * vec4(centerpos.xyz + rotY * ( addRadius * vecnorm + primitiveCoords ), 1.0); | |
g_uv.zw = dataIn[0].v_parameters.zw; | |
EmitVertex(); | |
} | |
#line 22000 | |
void main(){ | |
uint numVertices = dataIn[0].v_numvertices; | |
centerpos = dataIn[0].v_centerpos; | |
camdistance = length((cameraViewInv[3]).xyz - centerpos.xyz); | |
rotY = mat3(cameraViewInv[0].xyz,cameraViewInv[2].xyz, cameraViewInv[1].xyz); // swizzle cause we use xz | |
g_color = dataIn[0].v_color; | |
float length = dataIn[0].v_lengthwidthcornerheight.x; | |
float width = dataIn[0].v_lengthwidthcornerheight.y; | |
if (numVertices == uint(4)){ // A quad | |
offsetVertex4( width * 0.5, 0.0, length * 0.5, 0.0, 1.0); | |
offsetVertex4( width * 0.5, 0.0, -length * 0.5, 0.0, 0.0); | |
offsetVertex4(-width * 0.5, 0.0, length * 0.5, 1.0, 1.0); | |
offsetVertex4(-width * 0.5, 0.0, -length * 0.5, 1.0, 0.0); | |
EndPrimitive(); | |
} | |
} | |
]] | |
local fsSrc = | |
[[ | |
#version 420 | |
#extension GL_ARB_uniform_buffer_object : require | |
#extension GL_ARB_shading_language_420pack: require | |
//__ENGINEUNIFORMBUFFERDEFS__ | |
//__DEFINES__ | |
#line 30000 | |
uniform float addRadius; | |
uniform float iconDistance; | |
in DataGS { | |
vec4 g_color; | |
vec4 g_uv; | |
float camdistance; | |
}; | |
uniform sampler2D DrawPrimitiveAtUnitTexture; | |
uniform sampler2D mapDepths; | |
uniform sampler2D modelDepths; | |
uniform float stencilPass = 0.0; // 1 if we are stenciling | |
out vec4 fragColor; | |
#define RESOLUTION 3 | |
void main(void) | |
{ | |
vec4 texcolor = texture(DrawPrimitiveAtUnitTexture, g_uv.xy); | |
float sqrtdist = RESOLUTION * 1.4241; | |
vec2 screenUV = gl_FragCoord.xy/ viewGeometry.xy; | |
float mapdepth = texture(mapDepths, screenUV).x; | |
float modeldepth = texture(modelDepths, screenUV).x; | |
float fulldepth = min(mapdepth, modeldepth); | |
float deltadepth = max(mapdepth - modeldepth, 0.0); | |
float camdistancefact = clamp( camdistance/ 1000, 1.0, 3.0 ); | |
if (deltadepth > 0.0) discard; // we hit a model, bail! | |
//if ((modeldepth < 1.0) && (mapdepth > modeldepth)) discard; // model occluded behind map | |
if (stencilPass > 0.5){ | |
float nearest = RESOLUTION*20 + 1 ; | |
for (int x = -1 * RESOLUTION; x <= RESOLUTION; x++){ | |
for (int y = -1* RESOLUTION; y <= RESOLUTION; y++){ | |
//vec2 pixeloffset = vec2(float(x), float(y)) * sin(timeInfo.x * 0.1) * 2.0 ; | |
vec2 pixeloffset = vec2(float(x), float(y)); | |
vec2 screendelta = pixeloffset / viewGeometry.xy; | |
float mapd = texture(mapDepths, screenUV+ screendelta).x; | |
float modd = texture(modelDepths, screenUV + screendelta).x; | |
float dd = max(mapd - modd, 0.0); | |
if (dd > 0 ) nearest = min(nearest, length(pixeloffset)); | |
} | |
} | |
if (min(g_uv.x, g_uv.y) > 0.02){ // we are NOT the edges | |
//if (nearest > RESOLUTION*20 +1 ) discard; | |
//float stupidcolor = | |
fragColor.rgba = vec4(vec3(0.0), 1.0 - pow((nearest/(RESOLUTION * 1.4241 / camdistancefact)), 0.5)); | |
//fragColor.rgba = vec4(vec2(fract(gl_FragCoord.xy*0.1 )),0.0, 0.3); | |
}else{ | |
fragColor.rgba = vec4(vec3(fract(nearest/16)), 1.0); | |
fragColor.rgba = vec4(vec2(fract(gl_FragCoord.xy*0.1 )),0.0, 0.3); | |
} | |
}else{ | |
fragColor.rgba = vec4(vec2(fract(gl_FragCoord.xy*0.1 )),0.0, 0.3); | |
} | |
} | |
]] | |
local function goodbye(reason) | |
Spring.Echo("DrawPrimitiveAtUnits GL4 widget exiting with reason: "..reason) | |
widgetHandler:RemoveWidget() | |
end | |
local function InitDrawPrimitiveAtUnit(shaderConfig, DPATname) | |
local engineUniformBufferDefs = LuaShader.GetEngineUniformBufferDefs() | |
vsSrc = vsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) | |
fsSrc = fsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) | |
gsSrc = gsSrc:gsub("//__ENGINEUNIFORMBUFFERDEFS__", engineUniformBufferDefs) | |
DrawPrimitiveAtUnitShader = LuaShader( | |
{ | |
vertex = vsSrc:gsub("//__DEFINES__", LuaShader.CreateShaderDefinesString(shaderConfig)), | |
fragment = fsSrc:gsub("//__DEFINES__", LuaShader.CreateShaderDefinesString(shaderConfig)), | |
geometry = gsSrc:gsub("//__DEFINES__", LuaShader.CreateShaderDefinesString(shaderConfig)), | |
uniformInt = { | |
DrawPrimitiveAtUnitTexture = 0; | |
mapDepths = 1, | |
modelDepths = 2, | |
}, | |
uniformFloat = { | |
addRadius = 1, | |
iconDistance = 1, | |
}, | |
}, | |
DPATname .. "Shader GL4" | |
) | |
local shaderCompiled = DrawPrimitiveAtUnitShader:Initialize() | |
if not shaderCompiled then goodbye("Failed to compile ".. DPATname .." GL4 ") end | |
DrawPrimitiveAtUnitVBO = makeInstanceVBOTable( | |
{ | |
{id = 0, name = 'lengthwidthcorner', size = 4}, | |
{id = 1, name = 'teamID', size = 1, type = GL.UNSIGNED_INT}, | |
{id = 2, name = 'numvertices', size = 1, type = GL.UNSIGNED_INT}, | |
{id = 3, name = 'parameters', size = 4}, | |
{id = 4, name = 'uvoffsets', size = 4}, | |
{id = 5, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, | |
}, | |
64, -- maxelements | |
DPATname .. "VBO", -- name | |
5 -- unitIDattribID (instData) | |
) | |
if DrawPrimitiveAtUnitVBO == nil then goodbye("Failed to create DrawPrimitiveAtUnitVBO") end | |
local DrawPrimitiveAtUnitVAO = gl.GetVAO() | |
DrawPrimitiveAtUnitVAO:AttachVertexBuffer(DrawPrimitiveAtUnitVBO.instanceVBO) | |
DrawPrimitiveAtUnitVBO.VAO = DrawPrimitiveAtUnitVAO | |
return DrawPrimitiveAtUnitVBO, DrawPrimitiveAtUnitShader | |
end | |
local function initGL4() | |
--local DrawPrimitiveAtUnit = VFS.Include(luaShaderDir.."DrawPrimitiveAtUnit.lua") | |
--local InitDrawPrimitiveAtUnit = DrawPrimitiveAtUnit.InitDrawPrimitiveAtUnit | |
--local shaderConfig = DrawPrimitiveAtUnit.shaderConfig -- MAKE SURE YOU READ THE SHADERCONFIG TABLE in DrawPrimitiveAtUnit.lua | |
shaderConfig.TRANSPARENCY = 0.5 | |
shaderConfig.ANIMATION = 1 | |
shaderConfig.HEIGHTOFFSET = 3.99 | |
shaderConfig.BREATHERATE = 15.0 -- how fast it periodicly grows | |
shaderConfig.BREATHESIZE = 0.075 -- how much it periodicly grows | |
shaderConfig.MAXVERTICES = 4 -- how much it periodicly grows | |
shaderConfig.INITIALSIZE = 0.5 -- What size the stuff starts off at when spawned | |
shaderConfig.GROWTHRATE = 15 -- How fast it grows to full size | |
shaderConfig.POST_SHADING = "fragColor.rgba = texcolor;" | |
resurrectionHalosVBO, resurrectionHalosShader = InitDrawPrimitiveAtUnit(shaderConfig, "ResurrectionHalos") | |
end | |
function widget:VisibleUnitAdded(unitID, unitDefID, unitTeam) | |
--if unitConf[unitDefID] == nil or Spring.GetUnitRulesParam(unitID, "resurrected") == nil then return end | |
local gf = Spring.GetGameFrame() | |
myvisibleUnits[unitID] = unitDefID | |
pushElementInstance( | |
resurrectionHalosVBO, -- push into this Instance VBO Table | |
{ | |
--unitConf[unitDefID].iconSize, unitConf[unitDefID].iconSize, 8, unitConf[unitDefID].height , -- lengthwidthcornerheight | |
unitConf[unitDefID].scale * 16, unitConf[unitDefID].scale * 16, 8, unitConf[unitDefID].height*0.5 , -- lengthwidthcornerheight | |
0, -- teamID | |
4, -- how many trianges should we make (2 = cornerrect) | |
gf, 0, 0, 0, -- the gameFrame (for animations), and any other parameters one might want to add | |
0, 1, 0, 1, -- These are our default UV atlas tranformations | |
0, 0, 0, 0 -- these are just padding zeros, that will get filled in | |
}, | |
unitID, -- this is the key inside the VBO TAble, | |
true, -- update existing element | |
nil, -- noupload, dont use unless you know what you are doing | |
unitID -- last one should be UNITID? | |
) | |
end | |
function widget:VisibleUnitsChanged(extVisibleUnits, extNumVisibleUnits) | |
clearInstanceTable(resurrectionHalosVBO) | |
for unitID, unitDefID in pairs(extVisibleUnits) do | |
widget:VisibleUnitAdded(unitID, unitDefID, Spring.GetUnitTeam(unitID)) | |
end | |
end | |
function widget:VisibleUnitRemoved(unitID) | |
--Spring.Echo("widget:VisibleUnitRemoved",unitID) | |
if resurrectionHalosVBO.instanceIDtoIndex[unitID] then | |
popElementInstance(resurrectionHalosVBO, unitID) | |
myvisibleUnits[unitID] = nil | |
end | |
end | |
function widget:RecvLuaMsg(msg, playerID) | |
if msg:sub(1,18) == 'LobbyOverlayActive' then | |
chobbyInterface = (msg:sub(1,19) == 'LobbyOverlayActive1') | |
end | |
end | |
local GL_ALWAYS = GL.ALWAYS | |
local GL_EQUAL = GL.EQUAL | |
local GL_LINE_LOOP = GL.LINE_LOOP | |
local GL_KEEP = 0x1E00 --GL.KEEP | |
local GL_REPLACE = GL.REPLACE | |
local GL_DECR = 0x1E03 | |
local useStencil = true | |
local STENCILOPPASS = GL_DECR -- KEEP OR DECR | |
function widget:DrawWorld() | |
if chobbyInterface then return end | |
if Spring.IsGUIHidden() then | |
return | |
end | |
if resurrectionHalosVBO.usedElements > 0 then | |
gl.Texture(0, texture) | |
gl.Texture(1, "$map_gbuffer_zvaltex")-- Texture file | |
gl.Texture(2, "$model_gbuffer_zvaltex")-- Texture file | |
resurrectionHalosShader:Activate() | |
resurrectionHalosShader:SetUniform("iconDistance", 99999) -- pass | |
resurrectionHalosShader:SetUniform("addRadius", 0) | |
if useStencil then -- https://learnopengl.com/Advanced-OpenGL/Stencil-testing | |
gl.DepthMask(false) | |
-- FIRST PASS: | |
gl.ColorMask(false, false, false, false) -- disable writing to all but stencil | |
gl.StencilTest(true) -- enable stencil test | |
gl.DepthTest(false) -- dont do depth testing either | |
gl.StencilFunc(GL_ALWAYS, 1, 1) -- Always Passes, 1 Bit Plane, 1 As Mask | |
gl.StencilOp(GL_KEEP, GL_KEEP, GL_REPLACE) -- Set The Stencil Buffer To 1 Where Draw Any Polygon | |
--glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) | |
resurrectionHalosShader:SetUniform("stencilPass", 0) | |
resurrectionHalosVBO.VAO:DrawArrays(GL.POINTS, resurrectionHalosVBO.usedElements) | |
-- SECOND PASS | |
gl.ColorMask(true, true, true, true) | |
gl.StencilFunc(GL_EQUAL, 1, 0) | |
gl.StencilOp(GL_KEEP, GL_KEEP, STENCILOPPASS ) | |
resurrectionHalosShader:SetUniform("stencilPass", 1.0) | |
resurrectionHalosVBO.VAO:DrawArrays(GL.POINTS, resurrectionHalosVBO.usedElements) | |
else | |
gl.DepthTest(false) -- dont do depth testing either | |
gl.DepthMask(false) | |
resurrectionHalosShader:SetUniform("stencilPass", 1.0) | |
resurrectionHalosVBO.VAO:DrawArrays(GL.POINTS, resurrectionHalosVBO.usedElements) | |
end | |
resurrectionHalosShader:Deactivate() | |
--gl.TexRect(-1, -1, 1000, 1000 ) | |
gl.Clear( GL.STENCIL_BUFFER_BIT) | |
gl.Texture(0, false) | |
gl.Texture(1, false)-- Texture file | |
gl.Texture(2, false)-- Texture file | |
end | |
end | |
function widget:Initialize() | |
initGL4() | |
if WG['unittrackerapi'] and WG['unittrackerapi'].visibleUnits then | |
local visibleUnits = WG['unittrackerapi'].visibleUnits | |
for unitID, unitDefID in pairs(visibleUnits) do | |
widget:VisibleUnitAdded(unitID, unitDefID) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment