diff --git a/help_variables.json b/help_variables.json index ae24484f9..e025d678e 100644 --- a/help_variables.json +++ b/help_variables.json @@ -5167,7 +5167,7 @@ "default": "0", "desc": "Controls outlining of models and map. Disallowed in qcon ruleset.", "group-id": "35", - "remarks": "See /r_fx_geometry for edge outlining of the map.", + "remarks": "World outlines require vid_framebuffer 1|2 and Modern OpenGL (vid_renderer 1). With vid_renderer 0, ruleset default and sv_cheats 1, gl_outline 2 draws outlines of every surface that is rendered.", "type": "enum", "values": [ { @@ -5175,11 +5175,11 @@ "name": "0" }, { - "description": "Outline models to create rotoscope effect", + "description": "Outline models", "name": "1" }, { - "description": "Outline world (used only for map development)", + "description": "Outline world", "name": "2" }, { @@ -5188,6 +5188,66 @@ } ] }, + "gl_outline_color_world": { + "default": "0 0 0", + "desc": "Determines the color of world outlines.", + "group-id": "35", + "type": "string" + }, + "gl_outline_color_model": { + "default": "0 0 0", + "desc": "Determines the color of model outlines.", + "remarks": "Requires vid_renderer 1", + "group-id": "35", + "type": "string" + }, + "gl_outline_scale_model": { + "default": "1", + "desc": "Determines the scale of model outlines. Allows values 0 to 1 for rulesets smackdown and qcon, and 0 to 5 for other rulesets.", + "remarks": "Requires vid_renderer 1", + "group-id": "35", + "type": "float" + }, + "gl_outline_world_depth_threshold": { + "default": "4", + "desc": "Threshold for finding edges with depth values. Higher value means vanishing lines at close range. Lower value means false positives at longer range.", + "group-id": "35", + "type": "float" + }, + "gl_outline_use_player_color": { + "default": "0", + "desc": "Use the top and bottom color for drawing player outlines", + "remarks": "Requires vid_renderer 1", + "group-id": "35", + "type": "boolean" + }, + "gl_outline_color_team": { + "default": "", + "desc": "Determines the outline color of friendly players. Set to \"\" to use the value of gl_outline_color_model.", + "remarks": "Requires vid_renderer 1", + "group-id": "35", + "type": "string" + }, + "gl_outline_color_enemy": { + "default": "", + "desc": "Determines the outline color of enemy players. Set to \"\" to use the value of gl_outline_color_model.", + "remarks": "Requires vid_renderer 1", + "group-id": "35", + "type": "string" + }, + "gl_spec_xray": { + "default": "0", + "desc": "See players through walls (demo/qtv only). Is affected by gl_outline_* values like color, scale, etc.", + "remarks": "Requires vid_renderer 1", + "group-id": "35", + "type": "boolean" + }, + "gl_spec_xray_distance": { + "default": "512", + "desc": "Distance from which you can see the players through walls, see gl_spec_xray", + "group-id": "35", + "type": "float" + }, "gl_part_blobs": { "default": "0", "desc": "Determines particles used for blob explosions (EMPs).", @@ -15855,13 +15915,6 @@ } ] }, - "r_fx_geometry": { - "default": "0", - "desc": "Outlines the world based on surface normals. Disallowed in qcon ruleset.", - "group-id": "35", - "remarks": "Requires vid_framebuffer 1|2 and Modern OpenGL (vid_renderer 1). See /gl_outline for model outline", - "type": "boolean" - }, "r_glstats": { "default": "0", "desc": "When enabled, it creates a window in the top right of the screen showing the number of particles and etc. in use.", diff --git a/src/cl_ents.c b/src/cl_ents.c index 98b8e6478..652372171 100644 --- a/src/cl_ents.c +++ b/src/cl_ents.c @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "utils.h" #include "qmb_particles.h" #include "rulesets.h" +#include "teamplay.h" static int MVD_TranslateFlags(int src); void TP_ParsePlayerInfo(player_state_t *, player_state_t *, player_info_t *info); @@ -1651,7 +1652,11 @@ static qbool CL_AddVWepModel (entity_t *ent, int vw_index, int old_vw_frame) newent.skinnum = 0; newent.colormap = vid.colormap; newent.renderfx |= RF_PLAYERMODEL; // not really, but use same lighting rules + newent.renderfx |= RF_VWEPMODEL; + if(ent->renderfx & RF_BEHINDWALL) + newent.renderfx |= RF_BEHINDWALL; newent.effects = ent->effects; // Electro - added for shells + newent.scoreboard = ent->scoreboard; // for team color in gl_outline if ((!cls.mvdplayback || Cam_TrackNum() >= 0) && cl.racing && !CL_SetAlphaByDistance(&newent)) { return false; @@ -1684,16 +1689,17 @@ void CL_StorePausePredictionLocations(void) static void CL_LinkPlayers(void) { int j, msec, i, flicker, oldphysent; - float *org; - vec3_t tmp; + float *org, distance; + vec3_t tmp, end, diff; double playertime = CL_PlayerTime(); player_info_t *info; player_state_t *state, exact; entity_t ent; centity_t *cent; frame_t *frame; + trace_t trace; customlight_t cst_lt = {0}; - extern cvar_t cl_debug_antilag_ghost, cl_debug_antilag_view; + extern cvar_t cl_debug_antilag_ghost, cl_debug_antilag_view, gl_spec_xray_distance; frame = &cl.frames[cl.parsecount & UPDATE_MASK]; memset (&ent, 0, sizeof(entity_t)); @@ -1955,16 +1961,32 @@ static void CL_LinkPlayers(void) } } - if ((cl.vwep_enabled && r_drawvweps.value && state->vw_index) && (state->modelindex != cl_modelindices[mi_eyes])) + VectorCopy(cent->lerp_origin, end); + end[2] += 12; + trace = PM_TraceLine(r_refdef.vieworg, end); + + if (trace.fraction != 1) { + VectorSubtract(cent->lerp_origin, r_refdef.vieworg, diff); + distance = VectorLength(diff); + + if(distance > gl_spec_xray_distance.value) + continue; + else + ent.renderfx |= RF_BEHINDWALL; + } else + ent.renderfx &= ~RF_BEHINDWALL; + + ent.renderfx |= RF_PLAYERMODEL; + + if ((cl.vwep_enabled && r_drawvweps.value && state->vw_index) && (state->modelindex != cl_modelindices[mi_eyes])) { qbool vwep; vwep = CL_AddVWepModel (&ent, state->vw_index, cent->old_vw_frame); - if (vwep) + if (vwep) { if (cl.vw_model_name[0][0] != '-') { ent.model = cl.vw_model_precache[0]; - ent.renderfx |= RF_PLAYERMODEL; if (Cam_TrackNum() >= 0 && cl.racing) { CL_SetAlphaByDistance(&ent); } diff --git a/src/gl_framebuffer.c b/src/gl_framebuffer.c index 4223da3b0..ce6eb0603 100644 --- a/src/gl_framebuffer.c +++ b/src/gl_framebuffer.c @@ -38,7 +38,7 @@ extern cvar_t vid_framebuffer_hdr; extern cvar_t vid_framebuffer_blit; extern cvar_t vid_framebuffer_smooth; extern cvar_t vid_framebuffer_multisample; -extern cvar_t r_fx_geometry; +extern cvar_t gl_outline; static framebuffer_id VID_MultisampledAlternateId(framebuffer_id id); @@ -171,7 +171,7 @@ C_ASSERT(sizeof(framebuffer_multisample_alternate) / sizeof(framebuffer_multisam static framebuffer_data_t framebuffer_data[framebuffer_count]; -// +// GL_StaticProcedureDeclaration(glGenFramebuffers, "n=%d, ids=%p", GLsizei n, GLuint* ids) GL_StaticProcedureDeclaration(glDeleteFramebuffers, "n=%d, ids=%p", GLsizei n, GLuint* ids) GL_StaticProcedureDeclaration(glBindFramebuffer, "target=%u, framebuffer=%u", GLenum target, GLuint framebuffer) @@ -477,7 +477,7 @@ qbool GL_FramebufferStartWorldNormals(framebuffer_id id) GLenum buffers[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; float clearValue[] = { 0.0f, 0.0f, 0.0f, 0.0f }; - if (!r_fx_geometry.integer || !GL_Supported(R_SUPPORT_FRAMEBUFFERS) || !RuleSets_AllowEdgeOutline()) { + if (!(gl_outline.integer & 2) || !GL_Supported(R_SUPPORT_FRAMEBUFFERS) || !RuleSets_AllowEdgeOutline()) { return false; } diff --git a/src/gl_program.c b/src/gl_program.c index ec1c42500..2e5fd244a 100644 --- a/src/gl_program.c +++ b/src/gl_program.c @@ -326,6 +326,22 @@ static r_program_uniform_t program_uniforms[] = { { r_program_lightmap_compute, "firstLightmap", 1, false }, // r_program_uniform_turb_glc_fog_skyFogMix, { r_program_sky_glc, "skyFogMix", 1, false }, + // r_program_uniform_outline_color + { r_program_fx_world_geometry, "outline_color", 1, false }, + // r_program_uniform_outline_depth_threshold + { r_program_fx_world_geometry, "outline_depth_threshold", 1, false }, + // r_program_uniform_outline_scale + { r_program_fx_world_geometry, "outline_scale", 1, false }, + // r_program_uniform_aliasmodel_outline_color_model + { r_program_aliasmodel, "outline_color", 1, false }, + // r_program_uniform_aliasmodel_outline_color_team + { r_program_aliasmodel, "outline_color_team", 1, false }, + // r_program_uniform_aliasmodel_outline_color_enemy + { r_program_aliasmodel, "outline_color_enemy", 1, false }, + // r_program_uniform_aliasmodel_outline_use_player_color + { r_program_aliasmodel, "outline_use_player_color", 1, false }, + // r_program_uniform_aliasmodel_outline_scale + { r_program_aliasmodel, "outline_scale", 1, false }, }; #ifdef C_ASSERT diff --git a/src/glm_aliasmodel.c b/src/glm_aliasmodel.c index 465601081..0a714113a 100644 --- a/src/glm_aliasmodel.c +++ b/src/glm_aliasmodel.c @@ -45,6 +45,7 @@ typedef enum aliasmodel_draw_type_s { aliasmodel_draw_std, aliasmodel_draw_alpha, aliasmodel_draw_outlines, + aliasmodel_draw_outlines_spec, aliasmodel_draw_shells, aliasmodel_draw_postscene, aliasmodel_draw_postscene_additive, @@ -81,6 +82,8 @@ typedef struct uniform_block_aliasmodel_s { float modelViewMatrix[16]; // offset: 16 * float float color[4]; + float plrtopcolor[4]; + float plrbotcolor[4]; int amFlags; float yaw_angle_rad; float shadelight; @@ -106,15 +109,55 @@ extern float r_framelerp; static uniform_block_aliasmodels_t aliasdata; static int cached_mode; +static float cached_scale; +static int cached_use_plr_color; +static float cached_color_model[3]; +static float cached_color_enemy[3]; +static float cached_color_team[3]; -static void R_SetAliasModelUniform(int mode) +#define GET_COLOR_VALUES(colr) (float[]){(float)gl_outline_color_##colr.color[0] / 255.0f,(float)gl_outline_color_##colr.color[1] / 255.0f,(float)gl_outline_color_##colr.color[2] / 255.0f} + +static void R_SetAliasModelUniforms(int mode) { + extern cvar_t gl_outline_use_player_color, + gl_outline_color_model, gl_outline_color_team, gl_outline_color_enemy; + float scale = RuleSets_ModelOutlineScale(); + float *color_model, *color_enemy, *color_team; + int use_player_color = gl_outline_use_player_color.integer; + color_model = GET_COLOR_VALUES(model); + color_enemy = gl_outline_color_enemy.string[0] ? GET_COLOR_VALUES(enemy) : color_model; + color_team = gl_outline_color_team .string[0] ? GET_COLOR_VALUES(team) : color_model; + if (cached_mode != mode) { R_ProgramUniform1i(r_program_uniform_aliasmodel_drawmode, mode); cached_mode = mode; } + + if(cached_scale != scale) { + R_ProgramUniform1f(r_program_uniform_aliasmodel_outline_scale, scale); + cached_scale = scale; + } + + if(cached_use_plr_color != use_player_color) { + R_ProgramUniform1i(r_program_uniform_aliasmodel_outline_use_player_color, use_player_color); + cached_use_plr_color = use_player_color; + } + if(!VectorCompare(cached_color_model, color_model)) { + R_ProgramUniform3fv(r_program_uniform_aliasmodel_outline_color_model, color_model); + VectorCopy(color_model, cached_color_model); + } + if(!VectorCompare(cached_color_enemy, color_enemy)) { + R_ProgramUniform3fv(r_program_uniform_aliasmodel_outline_color_enemy, color_enemy); + VectorCopy(color_enemy, cached_color_enemy); + } + if(!VectorCompare(cached_color_team, color_team)) { + R_ProgramUniform3fv(r_program_uniform_aliasmodel_outline_color_team, color_team); + VectorCopy(color_team, cached_color_team); + } } +#undef GET_COLOR_VALUES + static int material_samplers_max; static int TEXTURE_UNIT_MATERIAL; static int TEXTURE_UNIT_CAUSTICS; @@ -269,6 +312,7 @@ static void GLM_QueueAliasModelDrawImpl( aliasmodel_draw_type_t shelltype = aliasmodel_draw_shells; aliasmodel_draw_instructions_t* instr; int textureSampler = -1; + extern cvar_t gl_spec_xray; // Compile here so we can work out how many samplers we have free to allocate per draw-call if (!GLM_CompileAliasModelProgram()) { @@ -321,7 +365,11 @@ static void GLM_QueueAliasModelDrawImpl( (effects & EF_BLUE ? AMF_SHELLMODEL_BLUE : 0) | (R_TextureReferenceIsValid(texture) ? AMF_TEXTURE_MATERIAL : 0) | (render_effects & RF_CAUSTICS ? AMF_CAUSTICS : 0) | - (render_effects & RF_WEAPONMODEL ? AMF_WEAPONMODEL : 0); + (render_effects & RF_WEAPONMODEL ? AMF_WEAPONMODEL : 0) | + (ent->scoreboard != NULL ? ent->scoreboard->teammate ? AMF_TEAMMATE : 0 : 0) | + (ent->renderfx & RF_BEHINDWALL ? AMF_BEHINDWALL : 0) | + (ent->renderfx & RF_PLAYERMODEL ? AMF_PLAYERMODEL : 0) | + (ent->renderfx & RF_VWEPMODEL ? AMF_VWEPMODEL : 0); uniform->yaw_angle_rad = ent->angles[YAW] * M_PI / 180.0; uniform->shadelight = ent->shadelight; uniform->ambientlight = ent->ambientlight; @@ -333,6 +381,14 @@ static void GLM_QueueAliasModelDrawImpl( uniform->materialSamplerMapping = textureSampler; uniform->minLumaMix = 1.0f - (ent->full_light ? bound(0, gl_fb_models.integer, 1) : 0); uniform->outline_normal_scale = ent->outlineScale; + if(ent->scoreboard != NULL) { + int tc = 16 * (bound(0, ent->scoreboard->topcolor, 13)) + 8; + int bc = 16 * (bound(0, ent->scoreboard->bottomcolor, 13)) + 8; + byte top[] = { host_basepal[tc * 3], host_basepal[tc * 3 + 1], host_basepal[tc * 3 + 2] }; + byte bot[] = { host_basepal[bc * 3], host_basepal[bc * 3 + 1], host_basepal[bc * 3 + 2] }; + for(int i = 0; i < 3; i++) uniform->plrtopcolor[i] = (float)top[i] / 256.0f; + for(int i = 0; i < 3; i++) uniform->plrbotcolor[i] = (float)bot[i] / 256.0f; + } // Add to queues GLM_QueueDrawCall(type, vbo_start, vbo_count, alias_draw_count); @@ -342,6 +398,11 @@ static void GLM_QueueAliasModelDrawImpl( if (shell) { GLM_QueueDrawCall(shelltype, vbo_start, vbo_count, alias_draw_count); } + if((render_effects & RF_PLAYERMODEL) && (render_effects & RF_BEHINDWALL) && + (cls.demoplayback || cls.mvdplayback) && gl_spec_xray.value) + { + GLM_QueueDrawCall(aliasmodel_draw_outlines_spec, vbo_start, vbo_count, alias_draw_count); + } alias_draw_count++; } @@ -453,7 +514,7 @@ static void GLM_RenderPreparedEntities(aliasmodel_draw_type_t type) extra_offset = buffers.BufferOffset(r_buffer_aliasmodel_drawcall_indirect); R_ProgramUse(r_program_aliasmodel); - R_SetAliasModelUniform(mode); + R_SetAliasModelUniforms(mode); // We have prepared the draw calls earlier in the frame so very trival logic here if (r_refdef2.drawCaustics) { renderer.TextureUnitBind(TEXTURE_UNIT_CAUSTICS, underwatertexture); @@ -488,11 +549,31 @@ static void GLM_RenderPreparedEntities(aliasmodel_draw_type_t type) ); } + if (type == aliasmodel_draw_std && alias_draw_instructions[aliasmodel_draw_outlines_spec].num_calls) { + instr = &alias_draw_instructions[aliasmodel_draw_outlines_spec]; + + R_TraceEnterNamedRegion("GLM_DrawOutlineBatch"); + R_SetAliasModelUniforms(EZQ_ALIAS_MODE_OUTLINES_SPEC); + + R_ApplyRenderingState(r_state_aliasmodel_outline_spec); + + for (i = 0; i < instr->num_calls; ++i) { + GL_MultiDrawArraysIndirect( + GL_TRIANGLES, + (const void*)(uintptr_t)(instr->indirect_buffer_offset + extra_offset), + instr->num_cmds[i], + 0 + ); + } + R_TraceLeaveNamedRegion(); + } + if (type == aliasmodel_draw_std && alias_draw_instructions[aliasmodel_draw_outlines].num_calls) { instr = &alias_draw_instructions[aliasmodel_draw_outlines]; R_TraceEnterNamedRegion("GLM_DrawOutlineBatch"); - R_SetAliasModelUniform(EZQ_ALIAS_MODE_OUTLINES); + R_SetAliasModelUniforms(EZQ_ALIAS_MODE_OUTLINES); + GLM_StateBeginAliasOutlineBatch(); for (i = 0; i < instr->num_calls; ++i) { @@ -503,7 +584,6 @@ static void GLM_RenderPreparedEntities(aliasmodel_draw_type_t type) 0 ); } - R_TraceLeaveNamedRegion(); } } diff --git a/src/glm_local.h b/src/glm_local.h index 59a75151e..64ab8cbce 100644 --- a/src/glm_local.h +++ b/src/glm_local.h @@ -70,7 +70,11 @@ typedef struct uniform_block_frame_constants_s { float skyFogMix; float fogMinZ; float fogMaxZ; - float padding; + // camangles [0] + + float camangles[3]; // [1] [2] + float padding[2]; + } uniform_block_frame_constants_t; #define MAX_WORLDMODEL_BATCH 64 @@ -113,7 +117,6 @@ void GLM_DrawAliasModelPostSceneBatches(void); void GLM_StateBeginPolyBlend(void); void GLM_StateBeginDraw3DSprites(void); -void GLM_StateBeginDrawWorldOutlines(void); void GLM_BeginDrawWorld(qbool alpha_surfaces, qbool polygon_offset); void GLM_UploadFrameConstants(void); diff --git a/src/glm_misc.c b/src/glm_misc.c index e2da89ad9..5454401ba 100644 --- a/src/glm_misc.c +++ b/src/glm_misc.c @@ -169,6 +169,10 @@ void GLM_SetupGL(void) memcpy(frameConstants.projectionMatrix, R_ProjectionMatrix(), sizeof(frameConstants.projectionMatrix)); VectorCopy(r_refdef.vieworg, frameConstants.position); + frameConstants.camangles[PITCH] = DEG2RAD(r_refdef.viewangles[PITCH]); + frameConstants.camangles[YAW] = DEG2RAD(r_refdef.viewangles[YAW]); + frameConstants.camangles[ROLL] = DEG2RAD(r_refdef.viewangles[ROLL]); + frameConstantsUploaded = false; } diff --git a/src/glm_rmain.c b/src/glm_rmain.c index 7b134ae26..8acd46d99 100644 --- a/src/glm_rmain.c +++ b/src/glm_rmain.c @@ -62,25 +62,38 @@ static void GLM_DrawWorldOutlines(void) if (R_TextureReferenceIsValid(normals) && GLM_CompileWorldGeometryProgram()) { int viewport[4]; int fullscreen_viewport[4]; + extern cvar_t gl_outline_color_world, gl_outline_world_depth_threshold; //, gl_outline_scale_world; + + R_GetViewport(viewport); // If we are only rendering to a section of the screen then that is the only part of the texture that will be filled in if (CL_MultiviewEnabled()) { - R_GetViewport(viewport); R_GetFullScreenViewport(fullscreen_viewport); R_Viewport(fullscreen_viewport[0], fullscreen_viewport[1], fullscreen_viewport[2], fullscreen_viewport[3]); R_EnableScissorTest(viewport[0], viewport[1], viewport[2], viewport[3]); + } else { + // ignore viewsize and allat crap and set the viewport size to the whole window. + // previously the viewport was already resized, and then resized again later, making the outlines not align. + R_Viewport(0, 0, VID_ScaledWidth3D(), VID_ScaledHeight3D()); } renderer.TextureUnitBind(0, normals); + R_ProgramUniform1f(r_program_uniform_outline_depth_threshold, gl_outline_world_depth_threshold.value); + // R_ProgramUniform1f(r_program_uniform_outline_scale, gl_outline_scale_world.value); + R_ProgramUniform3f(r_program_uniform_outline_color, + (float)gl_outline_color_world.color[0] / 255.0f, + (float)gl_outline_color_world.color[1] / 255.0f, + (float)gl_outline_color_world.color[2] / 255.0f); + R_ProgramUse(r_program_fx_world_geometry); R_ApplyRenderingState(r_state_fx_world_geometry); GL_DrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Restore viewport + R_Viewport(viewport[0], viewport[1], viewport[2], viewport[3]); if (CL_MultiviewEnabled()) { - R_Viewport(viewport[0], viewport[1], viewport[2], viewport[3]); R_DisableScissorTest(); } } diff --git a/src/glm_rsurf.c b/src/glm_rsurf.c index 26d727881..0e4b66a89 100644 --- a/src/glm_rsurf.c +++ b/src/glm_rsurf.c @@ -127,7 +127,7 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph { extern cvar_t gl_lumatextures; extern cvar_t gl_textureless; - extern cvar_t r_fx_geometry; + extern cvar_t gl_outline; qbool detail_textures = gl_detail.integer && R_TextureReferenceIsValid(detailtexture); qbool caustic_textures = r_refdef2.drawCaustics; @@ -147,7 +147,7 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph (r_drawflat.integer == 1 || r_drawflat.integer == 2 ? DRAW_FLATFLOORS : 0) | (r_drawflat.integer == 1 || r_drawflat.integer == 3 ? DRAW_FLATWALLS : 0) | (gl_textureless.integer ? DRAW_TEXTURELESS : 0) | - (r_fx_geometry.integer ? DRAW_GEOMETRY : 0) | + ((gl_outline.integer & 2) ? DRAW_GEOMETRY : 0) | (alpha_test ? DRAW_ALPHATESTED : 0); if (R_ProgramRecompileNeeded(program_id, drawworld_desiredOptions)) { @@ -202,7 +202,7 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph strlcat(included_definitions, va("#define SAMPLER_SKYDOME_TEXTURE %d\n", TEXTURE_UNIT_SKYDOME_TEXTURE), sizeof(included_definitions)); strlcat(included_definitions, va("#define SAMPLER_SKYDOME_CLOUDTEXTURE %d\n", TEXTURE_UNIT_SKYDOME_CLOUD_TEXTURE), sizeof(included_definitions)); } - if (r_fx_geometry.integer) { + if (gl_outline.integer & 2) { strlcat(included_definitions, "#define DRAW_GEOMETRY\n", sizeof(included_definitions)); } TEXTURE_UNIT_LIGHTMAPS = samplers++; @@ -579,53 +579,6 @@ qbool GLM_CompileSimple3dProgram(void) return R_ProgramReady(r_program_simple3d) && GLM_CompilePostProcessVAO(); } -static void GLM_DrawWorldModelOutlines(const glm_brushmodel_drawcall_t* drawcall) -{ - int begin = -1; - int i; - uintptr_t extra_offset = buffers.BufferOffset(r_buffer_brushmodel_drawcall_indirect); - float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; - - if (!GLM_CompileSimple3dProgram()) { - return; - } - - // - GLM_StateBeginDrawWorldOutlines(); - R_ProgramUse(r_program_simple3d); - R_ProgramUniform4fv(r_program_uniform_simple3d_color, color); - - for (i = 0; i < drawcall->batch_count; ++i) { - if (!drawcall->worldmodel_requests[i].worldmodel) { - if (begin >= 0) { - // Draw outline models so far - GL_MultiDrawElementsIndirect( - GL_TRIANGLE_STRIP, - GL_UNSIGNED_INT, - (void*)(extra_offset + begin * sizeof(drawcall->worldmodel_requests[0])), - i - begin, - sizeof(drawcall->worldmodel_requests[0]) - ); - } - begin = -1; - continue; - } - else if (begin < 0) { - begin = i; - } - } - if (begin >= 0) { - // Draw the rest - GL_MultiDrawElementsIndirect( - GL_TRIANGLE_STRIP, - GL_UNSIGNED_INT, - (void*)(extra_offset + begin * sizeof(drawcall->worldmodel_requests[0])), - drawcall->batch_count - begin, - sizeof(drawcall->worldmodel_requests[0]) - ); - } -} - static glm_brushmodel_drawcall_t* GL_FlushWorldModelBatch(void) { const glm_brushmodel_drawcall_t* prev; @@ -790,10 +743,6 @@ void GLM_DrawWorldModelBatch(glm_brushmodel_drawcall_type type) GLM_DrawWorldExecuteCalls(drawcall, extra_offset + drawcall->indirectDrawOffset, 0, drawcall->batch_count); } - if (R_DrawWorldOutlines()) { - GLM_DrawWorldModelOutlines(drawcall); - } - R_TraceLeaveNamedRegion(); frameStats.subdraw_calls += drawcall->batch_count; diff --git a/src/glm_state.c b/src/glm_state.c index d62b9dad5..cac58c308 100644 --- a/src/glm_state.c +++ b/src/glm_state.c @@ -23,16 +23,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_trace.h" #include "r_state.h" -void GLM_StateBeginDrawWorldOutlines(void) -{ - R_TraceEnterFunctionRegion; - - // FIXME: This was different for GLC & GLM, why? // disable depth-test - R_ApplyRenderingState(r_state_world_outline); - - R_TraceLeaveFunctionRegion; -} - void GLM_BeginDrawWorld(qbool alpha_surfaces, qbool polygon_offset) { if (alpha_surfaces && polygon_offset) { diff --git a/src/glsl/common.glsl b/src/glsl/common.glsl index 5b073e552..dc1955711 100644 --- a/src/glsl/common.glsl +++ b/src/glsl/common.glsl @@ -67,7 +67,11 @@ layout(std140, binding=EZQ_GL_BINDINGPOINT_FRAMECONSTANTS) uniform GlobalState { float skyFogMix; float fogMinZ; float fogMaxZ; - float padding; + // camAngles.x + + vec3 camAngles; // camAngles.yz + float padding1; + float padding2; }; struct WorldDrawInfo { @@ -95,6 +99,8 @@ struct AliasModelVert { struct AliasModel { mat4 modelView; vec4 color; + vec4 topcolor; + vec4 bottomcolor; int flags; float yaw_angle_rad; float shadelight; diff --git a/src/glsl/constants.glsl b/src/glsl/constants.glsl index 882f9925d..c15ac890d 100644 --- a/src/glsl/constants.glsl +++ b/src/glsl/constants.glsl @@ -14,7 +14,11 @@ #define AMF_TEXTURE_MATERIAL 16 #define AMF_TEXTURE_LUMA 32 #define AMF_WEAPONMODEL 64 -#define AMF_SHELLFLAGS (AMF_SHELLMODEL_RED | AMF_SHELLMODEL_BLUE | AMF_SHELLMODEL_GREEN) +#define AMF_PLAYERMODEL 128 +#define AMF_TEAMMATE 256 +#define AMF_BEHINDWALL 512 +#define AMF_VWEPMODEL 1024 +#define AMF_SHELLFLAGS (AMF_SHELLMODEL_RED | AMF_SHELLMODEL_BLUE | AMF_SHELLMODEL_GREEN) #define AM_VERTEX_NOLERP 1 // the alias model vertex should not be lerped, and always use lerpfraction 1 (meag: update shader if value no longer 1) #define AM_VERTEX_NORMALFIXED 2 // set after the alias model pose has been checked for matching vertices with different normals @@ -50,9 +54,10 @@ #define EZQ_GL_BINDINGPOINT_FRAMECONSTANTS 0 // Alias models -#define EZQ_ALIAS_MODE_NORMAL 0 -#define EZQ_ALIAS_MODE_SHELLS 1 -#define EZQ_ALIAS_MODE_OUTLINES 2 +#define EZQ_ALIAS_MODE_NORMAL 0 +#define EZQ_ALIAS_MODE_SHELLS 1 +#define EZQ_ALIAS_MODE_OUTLINES 2 +#define EZQ_ALIAS_MODE_OUTLINES_SPEC 4 // 8x8 block #define HW_LIGHTING_BLOCK_SIZE 4 diff --git a/src/glsl/draw_aliasmodel.fragment.glsl b/src/glsl/draw_aliasmodel.fragment.glsl index bb6e59f5d..cf107180a 100644 --- a/src/glsl/draw_aliasmodel.fragment.glsl +++ b/src/glsl/draw_aliasmodel.fragment.glsl @@ -3,6 +3,10 @@ #ezquake-definitions uniform int mode; +uniform vec3 outline_color; +uniform vec3 outline_color_team; +uniform vec3 outline_color_enemy; +uniform int outline_use_player_color; #ifdef DRAW_CAUSTIC_TEXTURES layout(binding=SAMPLER_CAUSTIC_TEXTURE) uniform sampler2D causticsTex; @@ -20,14 +24,43 @@ flat in int fsFlags; flat in int fsTextureEnabled; flat in int fsMaterialSampler; flat in float fsMinLumaMix; +flat in vec4 plrtopcolor; +flat in vec4 plrbotcolor; out vec4 frag_colour; +bool texture_coord_is_on_legs() { + // both front and back legs at the same y level on the texture + if(fsTextureCoord.y >= 0.42 && fsTextureCoord.y <= 1) + /* front legs */ /* back legs */ + return fsTextureCoord.x >= 0.19 && fsTextureCoord.x <= 0.4 || fsTextureCoord.x >= 0.69 && fsTextureCoord.x <= 0.9; + + return false; +} + void main() { - frag_colour = vec4(0, 0, 0, 1); + if((fsFlags & AMF_PLAYERMODEL) != 0) { + if(outline_use_player_color != 0) { + if((fsFlags & AMF_VWEPMODEL) != 0) { // vwep model is top color + frag_colour = vec4(plrtopcolor.rgb, 1.0f); + } else { + if (texture_coord_is_on_legs()) + frag_colour = vec4(plrbotcolor.rgb, 1.0f); + else + frag_colour = vec4(plrtopcolor.rgb, 1.0f); + } + } else { + if((fsFlags & AMF_TEAMMATE) != 0) + frag_colour = vec4(outline_color_team, 1.0f); + else + frag_colour = vec4(outline_color_enemy, 1.0f); + } + } else { + frag_colour = vec4(outline_color, 1.0f); + } - if (mode != EZQ_ALIAS_MODE_OUTLINES) { + if (mode != EZQ_ALIAS_MODE_OUTLINES && mode != EZQ_ALIAS_MODE_OUTLINES_SPEC) { vec4 tex = texture(samplers[fsMaterialSampler], fsTextureCoord.st); vec4 altTex = texture(samplers[fsMaterialSampler], fsAltTextureCoord.st); #ifdef DRAW_CAUSTIC_TEXTURES diff --git a/src/glsl/draw_aliasmodel.vertex.glsl b/src/glsl/draw_aliasmodel.vertex.glsl index 0fc90e57c..9711e9521 100644 --- a/src/glsl/draw_aliasmodel.vertex.glsl +++ b/src/glsl/draw_aliasmodel.vertex.glsl @@ -3,6 +3,7 @@ #ezquake-definitions uniform int mode; +uniform float outline_scale; layout(location = 0) in vec3 vboPosition; layout(location = 1) in vec2 vboTex; @@ -26,6 +27,8 @@ flat out int fsFlags; flat out int fsTextureEnabled; flat out int fsMaterialSampler; flat out float fsMinLumaMix; +flat out vec4 plrtopcolor; +flat out vec4 plrbotcolor; void main() { @@ -38,6 +41,9 @@ void main() fsFlags = models[_instanceId].flags; fsMinLumaMix = models[_instanceId].minLumaMix; + plrtopcolor = models[_instanceId].topcolor; + plrbotcolor = models[_instanceId].bottomcolor; + #ifdef EZQ_ALIASMODEL_MUZZLEHACK lerpFrac = sign(lerpFrac) * max(lerpFrac, (vboFlags & AM_VERTEX_NOLERP)); #endif @@ -65,8 +71,9 @@ void main() fsBaseColor = models[_instanceId].color; } } - else if (mode == EZQ_ALIAS_MODE_OUTLINES) { - gl_Position = projectionMatrix * models[_instanceId].modelView * vec4(position + models[_instanceId].outlineNormalScale * normalCoords, 1); + else if (mode == EZQ_ALIAS_MODE_OUTLINES || mode == EZQ_ALIAS_MODE_OUTLINES_SPEC) { + gl_Position = projectionMatrix * models[_instanceId].modelView * vec4(position + /*models[_instanceId].outlineNormalScale **/ normalCoords * outline_scale, 1); + fsTextureCoord = vec2(tex.x, tex.y); } else { gl_Position = projectionMatrix * models[_instanceId].modelView * vec4(position + normalCoords * 0.5, 1); diff --git a/src/glsl/draw_world.fragment.glsl b/src/glsl/draw_world.fragment.glsl index 1706b5701..e3f551cf8 100644 --- a/src/glsl/draw_world.fragment.glsl +++ b/src/glsl/draw_world.fragment.glsl @@ -97,7 +97,10 @@ void main() int turbType; #ifdef DRAW_GEOMETRY - normal_texture = vec4(Normal, mix(UnClipped.z / r_zFar, 0, min(1, Flags & EZQ_SURFACE_TYPE))); + int surface = Flags & EZQ_SURFACE_TYPE; + // if the texture is a turb, force outline between it and regular textures, but not between other turbs of the same type + float depth = (surface != 0) ? -float(surface) : abs(UnClipped.z / r_zFar); + normal_texture = vec4(Normal, depth); #endif if (draw_outlines == 1) { diff --git a/src/glsl/fx_world_geometry.fragment.glsl b/src/glsl/fx_world_geometry.fragment.glsl index 5e027c9b0..99e5ca473 100644 --- a/src/glsl/fx_world_geometry.fragment.glsl +++ b/src/glsl/fx_world_geometry.fragment.glsl @@ -4,6 +4,10 @@ layout(binding = 0) uniform sampler2D normal_texture; +uniform vec3 outline_color; +uniform float outline_scale; +uniform float outline_depth_threshold; + in vec2 TextureCoord; out vec4 frag_colour; @@ -11,25 +15,37 @@ void main() { ivec2 coords = ivec2(TextureCoord.x * r_width, TextureCoord.y * r_height); vec4 center = texelFetch(normal_texture, coords, 0); - vec4 left = texelFetchOffset(normal_texture, coords, 0, ivec2(-1, 0)); - vec4 right = texelFetchOffset(normal_texture, coords, 0, ivec2(+1, 0)); - vec4 up = texelFetchOffset(normal_texture, coords, 0, ivec2(0, -1)); - vec4 down = texelFetchOffset(normal_texture, coords, 0, ivec2(0, +1)); + vec4 left = texelFetch(normal_texture, coords - ivec2(1, 0), 0); + vec4 right = texelFetch(normal_texture, coords + ivec2(1, 0), 0); + vec4 up = texelFetch(normal_texture, coords - ivec2(0, 1), 0); + vec4 down = texelFetch(normal_texture, coords + ivec2(0, 1), 0); - bool z_diff = r_zFar * abs((right.a - center.a) - (center.a - left.a)) > 8; - bool z_diff2 = r_zFar * abs((down.a - center.a) - (center.a - up.a)) > 8; + bool ignore = center.a == left.a && center.a == right.a && center.a == up.a && center.a == down.a; + if(ignore) + discard; - if (center.a != 0 && ( - (left.a != 0 && right.a != 0 && z_diff) || - (down.a != 0 && up.a != 0 && z_diff2) || - (left.a != 0 && dot(center.rgb, left.rgb) < 0.9) || - (right.a != 0 && dot(center.rgb, right.rgb) < 0.9) || - (up.a != 0 && dot(center.rgb, up.rgb) < 0.9) || - (down.a != 0 && dot(center.rgb, down.rgb) < 0.9) - )) { - frag_colour = vec4(0, 0, 0, 1.0); + if(center.a == 0) + discard; + + if ((left.a != 0 && center.rgb != left.rgb ) || + (right.a != 0 && center.rgb != right.rgb) || + (up.a != 0 && center.rgb != up.rgb ) || + (down.a != 0 && center.rgb != down.rgb ) + ) { + frag_colour.rgb = outline_color; + frag_colour.a = 1; + return; } - else { - frag_colour = vec4(0, 0, 0, 0); + + bool z_diff = r_zFar * abs((right.a - center.a) - (center.a - left.a)) > outline_depth_threshold; + bool z_diff2 = r_zFar * abs((down.a - center.a) - (center.a - up.a)) > outline_depth_threshold; + + if (center.a != 0 && ( + (left.a != 0 && right.a != 0 && z_diff) || + (down.a != 0 && up.a != 0 && z_diff2) + )) { + frag_colour = vec4(outline_color, 1.0f); + return; } + frag_colour = vec4(0.0f); } diff --git a/src/menu_options.c b/src/menu_options.c index fde42f419..4c9291d6d 100644 --- a/src/menu_options.c +++ b/src/menu_options.c @@ -199,9 +199,6 @@ extern cvar_t mvd_autotrack, mvd_moreinfo, mvd_status, cl_weaponpreselect, cl_we enemyforceskins, teamforceskins, vid_vsync_lag_fix, cl_sayfilter_coloredtext, cl_sayfilter_sendboth, mvd_autotrack_lockteam, qtv_adjustbuffer, cl_earlypackets, cl_useimagesinfraglog, con_completion_format, menu_ingame, sys_inactivesound ; -#ifdef RENDERER_OPTION_MODERN_OPENGL -extern cvar_t r_fx_geometry; -#endif #ifdef _WIN32 extern cvar_t demo_format, sys_highpriority, cl_window_caption, vid_flashonactivity; @@ -995,9 +992,6 @@ setting settfps_arr[] = { ADDSET_BOOL ("Gib Filter", cl_gibfilter), ADDSET_ADVANCED_SECTION(), ADDSET_NAMED ("Dead Body Filter", cl_deadbodyfilter, deadbodyfilter_enum), -#ifdef RENDERER_OPTION_MODERN_OPENGL - ADDSET_BOOL ("Outline", r_fx_geometry), -#endif ADDSET_BASIC_SECTION(), ADDSET_SEPARATOR("Projectiles"), diff --git a/src/r_aliasmodel.c b/src/r_aliasmodel.c index 7dfa61a34..ad06c2a5b 100644 --- a/src/r_aliasmodel.c +++ b/src/r_aliasmodel.c @@ -264,7 +264,7 @@ void R_OverrideModelTextures(entity_t* ent, texture_ref* texture, texture_ref* f { int playernum = -1; - if (ent->scoreboard) { + if (ent->scoreboard && (ent->renderfx & RF_VWEPMODEL) == 0) { playernum = ent->scoreboard - cl.players; } diff --git a/src/r_program.h b/src/r_program.h index f9f03bee8..0ee954dc3 100644 --- a/src/r_program.h +++ b/src/r_program.h @@ -108,6 +108,14 @@ typedef enum { r_program_uniform_simple3d_color, r_program_uniform_lighting_firstLightmap, r_program_uniform_sky_glc_fog_skyFogMix, + r_program_uniform_outline_color, + r_program_uniform_outline_depth_threshold, + r_program_uniform_outline_scale, + r_program_uniform_aliasmodel_outline_color_model, + r_program_uniform_aliasmodel_outline_color_team, + r_program_uniform_aliasmodel_outline_color_enemy, + r_program_uniform_aliasmodel_outline_use_player_color, + r_program_uniform_aliasmodel_outline_scale, r_program_uniform_count } r_program_uniform_id; diff --git a/src/r_rmain.c b/src/r_rmain.c index ddaab77c4..dec47ff34 100644 --- a/src/r_rmain.c +++ b/src/r_rmain.c @@ -219,9 +219,20 @@ cvar_t gl_simpleitems = {"gl_simpleitems", "0"}; cvar_t gl_simpleitems_size = {"gl_simpleitems_size", "16"}; cvar_t gl_simpleitems_orientation = {"gl_simpleitems_orientation", "2"}; cvar_t gl_modulate = {"gl_modulate", "1"}; + cvar_t gl_outline = {"gl_outline", "0"}; +cvar_t gl_outline_color_world = {"gl_outline_color_world", "0 0 0"}; +cvar_t gl_outline_color_model = {"gl_outline_color_model", "0 0 0"}; +cvar_t gl_outline_scale_world = {"gl_outline_scale_world", "1"}; +cvar_t gl_outline_scale_model = {"gl_outline_scale_model", "1"}; +cvar_t gl_outline_world_depth_threshold = {"gl_outline_world_depth_threshold", "4"}; +cvar_t gl_outline_use_player_color = {"gl_outline_use_player_color", "0"}; +cvar_t gl_spec_xray = {"gl_spec_xray", "0"}; +cvar_t gl_spec_xray_distance = {"gl_spec_xray_distance", "1500"}; +cvar_t gl_outline_color_team = {"gl_outline_color_team", ""}; +cvar_t gl_outline_color_enemy = {"gl_outline_color_enemy", ""}; + cvar_t gl_smoothmodels = {"gl_smoothmodels", "1"}; -cvar_t r_fx_geometry = {"r_fx_geometry", "0"}; cvar_t gl_vbo_clientmemory = {"gl_vbo_clientmemory", "0", CVAR_LATCH_GFX }; @@ -698,10 +709,18 @@ void R_Init(void) Cvar_Register(&gl_modulate); Cvar_Register(&gl_outline); + Cvar_Register(&gl_outline_color_world); + Cvar_Register(&gl_outline_color_model); + // Cvar_Register(&gl_outline_scale_world); + Cvar_Register(&gl_outline_scale_model); + Cvar_Register(&gl_outline_world_depth_threshold); + Cvar_Register(&gl_outline_use_player_color); + Cvar_Register(&gl_spec_xray); + Cvar_Register(&gl_spec_xray_distance); + Cvar_Register(&gl_outline_color_team); + Cvar_Register(&gl_outline_color_enemy); Cvar_Register(&gl_smoothmodels); - Cvar_Register(&r_fx_geometry); - Cvar_Register(&gl_vbo_clientmemory); Cvar_SetCurrentGroup(CVAR_GROUP_SCREEN); diff --git a/src/r_state.h b/src/r_state.h index 3a8483368..db62f4cb5 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -289,6 +289,7 @@ typedef enum { r_state_aliasmodel_shadows, r_state_aliasmodel_outline, + r_state_aliasmodel_outline_spec, r_state_weaponmodel_outline, r_state_aliasmodel_opaque_batch, diff --git a/src/r_states.c b/src/r_states.c index 60fd5f602..0bf48f0c1 100644 --- a/src/r_states.c +++ b/src/r_states.c @@ -293,6 +293,10 @@ static void R_InitialiseEntityStates(void) state->blendingEnabled = false; state->cullface.mode = r_cullface_back; + state = R_CopyRenderingState(r_state_aliasmodel_outline_spec, r_state_aliasmodel_outline, "aliasmodel-outline-spec"); + state->cullface.mode = r_cullface_front; + state->depth.test_enabled = false; + state = R_CopyRenderingState(r_state_weaponmodel_outline, r_state_aliasmodel_outline, "weaponmodel-outline"); state->depth.farRange = R_UseImmediateOpenGL() ? 0.3f : state->depth.farRange; diff --git a/src/render.h b/src/render.h index 8d503cf95..90f6b89b3 100644 --- a/src/render.h +++ b/src/render.h @@ -42,6 +42,8 @@ typedef struct efrag_s { #define RF_ADDITIVEBLEND 128 #define RF_ROCKETPACK 256 #define RF_LGPACK 512 +#define RF_BEHINDWALL 1024 +#define RF_VWEPMODEL 2048 #define RF_BACKPACK_FLAGS (RF_ROCKETPACK | RF_LGPACK) diff --git a/src/rulesets.c b/src/rulesets.c index 8aa6aa67e..1a4c39b95 100644 --- a/src/rulesets.c +++ b/src/rulesets.c @@ -81,7 +81,7 @@ qbool RuleSets_DisallowSimpleTexture(model_t* mod) } } -// for models (gl_outline) +// for models (gl_outline 1 and 3) qbool RuleSets_DisallowModelOutline(struct model_s *mod) { if (mod == NULL) { @@ -102,7 +102,20 @@ qbool RuleSets_DisallowModelOutline(struct model_s *mod) } } -// for edges (r_fx_geometry) +// gl_outline_scale_model +// 0-1 for smackdown and qcon, 0-5 for others +float RuleSets_ModelOutlineScale(void) { + extern cvar_t gl_outline_scale_model; + switch(rulesetDef.ruleset) { + case rs_smackdown: + case rs_qcon: + return bound(0.0f, gl_outline_scale_model.value, 1.0f); + default: + return bound(0.0f, gl_outline_scale_model.value, 5.0f); + } +} + +// for edges (gl_outline 2 and 3) qbool RuleSets_AllowEdgeOutline(void) { switch(rulesetDef.ruleset) { diff --git a/src/rulesets.h b/src/rulesets.h index 4ebe19a33..a09f56fb7 100644 --- a/src/rulesets.h +++ b/src/rulesets.h @@ -61,6 +61,7 @@ qbool Ruleset_BlockHudPicChange(void); qbool Ruleset_AllowPolygonOffset(entity_t* ent); qbool Rulesets_AllowAlternateModel(const char* modelName); qbool RuleSets_DisallowModelOutline(struct model_s *mod); +float RuleSets_ModelOutlineScale(void); qbool RuleSets_AllowEdgeOutline(void); qbool RuleSets_DisallowExternalTexture(struct model_s *mod); qbool Ruleset_IsLumaAllowed(struct model_s *mod);