From 3043a8cde2e2ca3aef438fd32a67cc5701744af8 Mon Sep 17 00:00:00 2001 From: JFronny <33260128+jfronny@users.noreply.github.com> Date: Thu, 26 Nov 2020 20:11:38 +0100 Subject: [PATCH] Enums, example based on LumiLights, README --- .gitignore | 8 +- README.md | 37 ++ .../shaders/internal/material_main.frag | 377 ++++++++++++++++++ .../shaders/internal/material_main.vert | 89 +++++ .../internal/process/emissive_color.frag | 30 ++ .../assets/lumi/materials/tasty_liquid.json | 7 + .../lumi/assets/lumi/shaders/api/varying.glsl | 2 + .../lumi/assets/lumi/shaders/lib/noise.glsl | 7 + .../assets/lumi/shaders/material/water.frag | 38 ++ .../assets/lumi/shaders/material/water.vert | 8 + .../materialmaps/fluid/flowing_water.json | 3 + .../minecraft/materialmaps/fluid/water.json | 3 + .../lumi/assets/respackopts/conf.json | 13 + run/resourcepacks/lumi/pack.mcmeta | 6 + run/resourcepacks/lumi/pack.png | Bin 0 -> 24229 bytes .../jfronny/respackopts/GuiFactory.java | 49 ++- .../io/gitlab/jfronny/respackopts/MMI.java | 5 +- .../jfronny/respackopts/Respackopts.java | 41 +- .../respackopts/integration/FrexCompat.java | 48 ++- .../mixin/ResourcePackManagerMixin.java | 40 +- .../assets/respackopts/lang/en_us.json | 3 +- 21 files changed, 751 insertions(+), 63 deletions(-) create mode 100644 README.md create mode 100644 run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.frag create mode 100644 run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.vert create mode 100644 run/resourcepacks/lumi/assets/canvas/shaders/internal/process/emissive_color.frag create mode 100644 run/resourcepacks/lumi/assets/lumi/materials/tasty_liquid.json create mode 100644 run/resourcepacks/lumi/assets/lumi/shaders/api/varying.glsl create mode 100644 run/resourcepacks/lumi/assets/lumi/shaders/lib/noise.glsl create mode 100644 run/resourcepacks/lumi/assets/lumi/shaders/material/water.frag create mode 100644 run/resourcepacks/lumi/assets/lumi/shaders/material/water.vert create mode 100644 run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/flowing_water.json create mode 100644 run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/water.json create mode 100644 run/resourcepacks/lumi/assets/respackopts/conf.json create mode 100644 run/resourcepacks/lumi/pack.mcmeta create mode 100644 run/resourcepacks/lumi/pack.png diff --git a/.gitignore b/.gitignore index 3c37caf..70af2d3 100644 --- a/.gitignore +++ b/.gitignore @@ -112,7 +112,11 @@ gradle-app.setting **/build/ # Common working directory -run/ +!/run +/run/* +!/run/resourcepacks +/run/resourcepacks/* +!/run/resourcepacks/lumi # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar +!gradle-wrapper.jar \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..db1d400 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Respackopts +Respackopts provides resource packs with config menus\ +By default it integrates with frex (canvas shaders) and libcd (use this as reference)\ +An example for the frex/canvas integration can be found [here](https://gitlab.com/JFronny/respackopts/-/tree/master/run/resourcepacks/lumi) + +# Using Respackopts +## Users +You will just need to install Respackopts. A menu button will appear besides all supported resourcepacks. +## Resource pack authors +You will need to define a respackopts conf as seen [here](https://gitlab.com/JFronny/respackopts/-/blob/master/run/resourcepacks/lumi/assets/respackopts/conf.json) + +This config may include: +- boolean values, expressed as Json bools +- number values, expressed as Json numbers +- string values (not accessible from default integrations), expressed as Json strings +- enums, expressed as Json arrays containing simple strings + +The version property defines the format version. It will be incremented if changes to the layout of the config are made\ +I will likely attempt to support older versions if I decide to make an incompatible change.\ +To use the information from the config you will need to use and integration module/plugin.\ +For the ones included by default: +### Canvas +Respackopts defines a config supplier, you can include it with `#include respackopts:config_supplier`\ +Values can be accessed with `packId_entryId` for basic elements.\ +Enum values can be accessed with `packId_entryId_enumKey`.\ +Examples of using these are available [here](https://gitlab.com/JFronny/respackopts/-/tree/master/run/resourcepacks/lumi) +### LibCD +The LibCD integration defines a condition named `respackopts:cfg` that takes a string describing the location of a bool as `packId:entryId` + +There have been issues with LibCD in development, so it might be smart not to use it. +## Mod developers +All data is available in static HashMaps in `io.gitlab.jfronny.respackopts.Respackopts`.\ +To save information, call `Respackopts.save()`, `Respackopts.load()` to load. +`boolVals`, `numVals`, `strVals` and `enumKeys` use the resource pack ID as their first key, +then the property name. After that you get the value. Enums are expressed as numbers with enum key names in enumKeys.\ +If you need code to be ran after changes are made to the state, add an action to `saveActions`.\ +This is currently used for the Canvas/FREX integration, which uses it to generate shader code. \ No newline at end of file diff --git a/run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.frag b/run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.frag new file mode 100644 index 0000000..8b9231a --- /dev/null +++ b/run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.frag @@ -0,0 +1,377 @@ +/* + * Lumi Lights - A shader pack for Canvas + * Copyright (c) 2020 spiralhalo and Contributors + * + * See `README.md` for license notice. + */ + +#include canvas:shaders/internal/header.glsl +#include canvas:shaders/internal/varying.glsl +#include canvas:shaders/internal/diffuse.glsl +#include canvas:shaders/internal/flags.glsl +#include canvas:shaders/internal/fog.glsl +#include frex:shaders/api/world.glsl +#include frex:shaders/api/player.glsl +#include frex:shaders/api/material.glsl +#include frex:shaders/api/fragment.glsl +#include frex:shaders/api/sampler.glsl +#include frex:shaders/lib/math.glsl +#include frex:shaders/lib/color.glsl +#include canvas:shaders/internal/program.glsl +#include lumi:shaders/api/varying.glsl + +#include canvas:apitarget +#include respackopts:config_supplier + +/****************************************************** + canvas:shaders/internal/material_main.frag +******************************************************/ + +#define M_PI 3.1415926535897932384626433832795 + +const float hdr_sunStr = 3; +const float hdr_moonStr = 0.4; +const float hdr_blockStr = 1.5; +const float hdr_handHeldStr = 0.9; +const float hdr_skylessStr = 0.2; +const float hdr_baseMinStr = 0.0; +const float hdr_baseMaxStr = 0.25; +const float hdr_emissiveStr = 1; +const float hdr_relAmbient = 0.09; +const float hdr_relSunHorizon = 0.5; +const float hdr_zWobbleDefault = 0.1; +const float hdr_finalMult = 1; +const float hdr_gamma = 2.2; + +float hdr_gammaAdjust(float x){ + return pow(x, hdr_gamma); +} + +vec3 hdr_gammaAdjust(vec3 x){ + return pow(x, vec3(hdr_gamma)); +} + +vec3 hdr_reinhardJodieTonemap(in vec3 v) { + float l = frx_luminance(v); + vec3 tv = v / (1.0f + v); + return mix(v / (1.0f + l), tv, tv); +} + +vec3 hdr_vibrantTonemap(in vec3 v){ + return v / (frx_luminance(v) + vec3(1.0)); +} + +void _cv_startFragment(inout frx_FragmentData data) { + int cv_programId = _cv_fragmentProgramId(); +#include canvas:startfragment +} + +float l2_clampScale(float e0, float e1, float v){ + return clamp((v-e0)/(e1-e0), 0.0, 1.0); +} + +float l2_max3(vec3 vec){ + return max(vec.x, max(vec.y, vec.z)); +} + +// vec3 l2_what(vec3 rgb){ +// return vec3(0.4123910 * rgb.r + 0.3575840 * rgb.g + 0.1804810 * rgb.b, +// 0.2126390 * rgb.r + 0.7151690 * rgb.g + 0.0721923 * rgb.b, +// 0.0193308 * rgb.r + 0.1191950 * rgb.g + 0.9505320 * rgb.b); +// } + +vec3 l2_blockLight(float blockLight){ + float bl = l2_clampScale(0.03125, 1.0, blockLight); + bl *= bl * hdr_blockStr; + vec3 block = hdr_gammaAdjust(vec3(bl, bl*0.875, bl*0.75)); + +#if HANDHELD_LIGHT_RADIUS != 0 + vec4 held = frx_heldLight(); + if (held.w > 0.0) { + float hl = l2_clampScale(held.w * HANDHELD_LIGHT_RADIUS, 0.0, gl_FogFragCoord); + hl *= hl * hdr_handHeldStr; + + return block + hdr_gammaAdjust(held.rgb * hl); + } +#endif + + return block; +} + +vec3 l2_emissiveLight(float emissivity){ + return vec3(hdr_gammaAdjust(emissivity) * hdr_emissiveStr); +} + +float l2_skyLight(float skyLight, float intensity) +{ + float sl = l2_clampScale(0.03125, 1.0, skyLight); + return hdr_gammaAdjust(sl) * intensity; +} + +vec3 l2_ambientColor(float time){ + vec3 ambientColor = hdr_gammaAdjust(vec3(0.6, 0.9, 1.0)) * hdr_sunStr * hdr_relAmbient; + vec3 sunriseAmbient = hdr_gammaAdjust(vec3(1.0, 0.8, 0.4)) * hdr_sunStr * hdr_relAmbient * hdr_relSunHorizon; + vec3 sunsetAmbient = hdr_gammaAdjust(vec3(1.0, 0.6, 0.2)) * hdr_sunStr * hdr_relAmbient * hdr_relSunHorizon; + vec3 nightAmbient = hdr_gammaAdjust(vec3(1.0, 1.0, 2.0)) * hdr_moonStr * hdr_relAmbient; + if(time > 0.94){ + ambientColor = mix(nightAmbient, sunriseAmbient, l2_clampScale(0.94, 0.98, time)); + } else if(time > 0.52){ + ambientColor = mix(sunsetAmbient, nightAmbient, l2_clampScale(0.52, 0.56, time)); + } else if(time > 0.48){ + ambientColor = mix(ambientColor, sunsetAmbient, l2_clampScale(0.48, 0.5, time)); + } else if(time < 0.02){ + ambientColor = mix(ambientColor, sunriseAmbient, l2_clampScale(0.02, 0, time)); + } + return ambientColor; +} + +vec3 l2_skyAmbient(float skyLight, float time, float intensity){ + float sa = l2_skyLight(skyLight, intensity) * 2.5; + return sa * l2_ambientColor(time); +} + +float l2_userBrightness(){ + float base = texture2D(frxs_lightmap, vec2(0.03125, 0.03125)).r; + // if(frx_isWorldTheNether()){ + // return smoothstep(0.15/*0.207 no true darkness in nether*/, 0.577, base); + // } else if (frx_isWorldTheEnd(){ + // return smoothstep(0.18/*0.271 no true darkness in the end*/, 0.685, base); + // } else { + // return smoothstep(0.053, 0.135, base); + // } + + // Simplify nether/the end check + if(frx_worldHasSkylight()){ + return smoothstep(0.053, 0.135, base); + } else { + return smoothstep(0.15, 0.63, base); + } +} + +vec3 l2_skylessLightColor(){ + return hdr_gammaAdjust(vec3(1.0)); +} + +vec3 l2_dimensionColor(){ + if (frx_isWorldTheNether()) { + float min_col = min(min(gl_Fog.color.rgb.x, gl_Fog.color.rgb.y), gl_Fog.color.rgb.z); + float max_col = max(max(gl_Fog.color.rgb.x, gl_Fog.color.rgb.y), gl_Fog.color.rgb.z); + float sat = 0.0; + if (max_col != 0.0) { + sat = (max_col-min_col)/max_col; + } + + return hdr_gammaAdjust(clamp((gl_Fog.color.rgb*(1/max_col))+pow(sat,2)/2, 0.0, 1.0)); + } + else { + return hdr_gammaAdjust(vec3(0.8, 0.7, 1.0)); + } +} + +vec3 l2_skylessLight(vec3 normal){ + if(frx_worldHasSkylight()){ + return vec3(0); + } else { + float yalign = dot(normal,vec3(0, 0.977358, 0.211593)); // a bit towards z for more interesting effect + yalign = frx_isSkyDarkened()?abs(yalign):max(0,yalign); + return yalign * hdr_skylessStr * l2_skylessLightColor() * l2_userBrightness(); + } +} + +vec3 l2_baseAmbient(){ + if(frx_worldHasSkylight()){ + return vec3(0.1) * mix(hdr_baseMinStr, hdr_baseMaxStr, l2_userBrightness()); + } else { + return l2_dimensionColor() * mix(hdr_baseMinStr, hdr_baseMaxStr, l2_userBrightness()); + } +} + +vec3 l2_sunColor(float time){ + vec3 sunColor = hdr_gammaAdjust(vec3(1.0, 1.0, lumi_sunColorFactor)) * hdr_sunStr; + vec3 sunriseColor = hdr_gammaAdjust(vec3(1.0, 0.8, 0.4)) * hdr_sunStr * hdr_relSunHorizon; + vec3 sunsetColor = hdr_gammaAdjust(vec3(1.0, 0.6, 0.4)) * hdr_sunStr * hdr_relSunHorizon; + if(time > 0.94){ + sunColor = sunriseColor; + } else if(time > 0.56){ + sunColor = vec3(0); // pitch black at night + } else if(time > 0.54){ + sunColor = mix(sunsetColor, vec3(0), l2_clampScale(0.54, 0.56, time)); + } else if(time > 0.5){ + sunColor = sunsetColor; + } else if(time > 0.48){ + sunColor = mix(sunColor, sunsetColor, l2_clampScale(0.48, 0.5, time)); + } else if(time < 0.02){ + sunColor = mix(sunColor, sunriseColor, l2_clampScale(0.02, 0, time)); + } + return sunColor; +} + +vec3 l2_vanillaSunDir(in float time, float zWobble){ + + // wrap time to account for sunrise + time -= (time >= 0.75) ? 1.0 : 0.0; + + // supposed offset of sunset/sunrise from 0/12000 daytime. might get better result with datamining? + float sunHorizonDur = 0.04; + + // angle of sun in radians + float angleRad = l2_clampScale(-sunHorizonDur, 0.5+sunHorizonDur, time) * M_PI; + + return normalize(vec3(cos(angleRad), sin(angleRad), zWobble)); +} + +vec3 l2_sunLight(float skyLight, in float time, float intensity, float rainGradient, vec3 diffuseNormal){ + + // wrap time to account for sunrise + float customTime = (time >= 0.75) ? (time - 1.0) : time; + + float customIntensity = l2_clampScale(-0.08, 0.00, customTime); + + if(customTime >= 0.25){ + customIntensity = l2_clampScale(0.58, 0.5, customTime); + } + + customIntensity *= mix(1.0, 0.0, rainGradient); + + float sl = l2_skyLight(skyLight, max(customIntensity, intensity)); + + // direct sun light doesn't reach into dark spot as much as sky ambient + sl = frx_smootherstep(0.5,1.0,sl); + + // zWobble is added to make more interesting looking diffuse light + // TODO: might be fun to use frx_worldDay() with sine wave for the zWobble to simulate annual sun position change + sl *= max(0.0, dot(l2_vanillaSunDir(time, hdr_zWobbleDefault), diffuseNormal)); + return sl * l2_sunColor(time); +} + +vec3 l2_moonLight(float skyLight, float time, float intensity, vec3 diffuseNormal){ + float ml = l2_skyLight(skyLight, intensity) * frx_moonSize() * hdr_moonStr; + float aRad = l2_clampScale(0.56, 0.94, time) * M_PI; + ml *= max(0.0, dot(vec3(cos(aRad), sin(aRad), 0), diffuseNormal)); + if(time < 0.58){ + ml *= l2_clampScale(0.54, 0.58, time); + } else if(time > 0.92){ + ml *= l2_clampScale(0.96, 0.92, time); + } + return vec3(ml); +} + +float l2_specular(float time, vec3 aNormal, vec3 aPos, vec3 cameraPos, float power) +{ + // calculate sun position (0 zWobble to make it look accurate with vanilla sun visuals) + vec3 sunDir = l2_vanillaSunDir(time, 0); + + // obtain the direction of the camera + vec3 viewDir = normalize(cameraPos - aPos); + + // calculate the specular light + return pow(max(0.0, dot(reflect(-sunDir, aNormal), viewDir)),power); +} + +float l2_ao(frx_FragmentData fragData) { +#if AO_SHADING_MODE != AO_MODE_NONE + float ao = fragData.ao ? _cvv_ao : 1.0; + return hdr_gammaAdjust(min(1.0, ao + fragData.emissivity)); +#else + return 1.0; +#endif +} + +void main() { + frx_FragmentData fragData = frx_FragmentData ( + texture2D(frxs_spriteAltas, _cvv_texcoord, _cv_getFlag(_CV_FLAG_UNMIPPED) * -4.0), + _cvv_color, + frx_matEmissive() ? 1.0 : 0.0, + !frx_matDisableDiffuse(), + !frx_matDisableAo(), + _cvv_normal, + _cvv_lightcoord + ); + + _cv_startFragment(fragData); + + vec4 a = fragData.spriteColor * fragData.vertexColor; + float bloom = fragData.emissivity; // separate bloom from emissivity + + if(frx_isGui()){ +#if DIFFUSE_SHADING_MODE != DIFFUSE_MODE_NONE + if(fragData.diffuse){ + float diffuse = mix(_cvv_diffuse, 1, fragData.emissivity); + vec3 shading = mix(vec3(0.5, 0.4, 0.8) * diffuse * diffuse, vec3(1.0), diffuse); + a.rgb *= shading; + } +#endif + } else { + a.rgb = hdr_gammaAdjust(a.rgb); + + // If diffuse is disabled (e.g. grass) then the normal points up by default + float ao = l2_ao(fragData); + vec3 diffuseNormal = fragData.diffuse?fragData.vertexNormal * frx_normalModelMatrix():vec3(0,1,0); + vec3 block = l2_blockLight(fragData.light.x); + vec3 sun = l2_sunLight(fragData.light.y, frx_worldTime(), frx_ambientIntensity(), frx_rainGradient(), diffuseNormal); + vec3 moon = l2_moonLight(fragData.light.y, frx_worldTime(), frx_ambientIntensity(), diffuseNormal); + vec3 skyAmbient = l2_skyAmbient(fragData.light.y, frx_worldTime(), frx_ambientIntensity()); + vec3 emissive = l2_emissiveLight(fragData.emissivity); + vec3 nether = l2_skylessLight(diffuseNormal); + + vec3 light = block + moon + l2_baseAmbient() + skyAmbient + sun + nether; + light *= ao; // AO is supposed to be applied to ambient only, but things look better with AO on everything except for emissive light + light += emissive; + + vec3 specular = vec3(0.0); + if (wwv_specPower > 0.01) { + vec3 specularNormal = fragData.vertexNormal * frx_normalModelMatrix(); + + float skyAccess = smoothstep(0.89, 1.0, fragData.light.y); + + vec3 fragPos = frx_var0.xyz; + vec3 cameraPos = frx_var1.xyz; + vec3 sunDir = l2_vanillaSunDir(frx_worldTime(), 0); + vec3 sun = l2_sunLight(fragData.light.y, frx_worldTime(), frx_ambientIntensity(), frx_rainGradient(), sunDir); + + float specularAmount = l2_specular(frx_worldTime(), specularNormal, fragPos, cameraPos, wwv_specPower); + + specular = sun * specularAmount * skyAccess; + } + + a.rgb *= light; + a.rgb += specular; + + float specularLuminance = frx_luminance(specular); + a.a += specularLuminance; + bloom += specularLuminance; + + a.rgb *= hdr_finalMult; +#if lumi_tonemap == lumi_tonemap_vibrant + a.rgb = pow(hdr_vibrantTonemap(a.rgb), vec3(1.0 / hdr_gamma)); +#elif lumi_tonemap == reinhardJodie + a.rgb = pow(hdr_reinhardJodieTonemap(a.rgb), vec3(1.0 / hdr_gamma)); +#else + a.rgb = pow(frx_toneMap(a.rgb), vec3(1.0 / hdr_gamma)); +#endif + } + + // PERF: varyings better here? + if (_cv_getFlag(_CV_FLAG_CUTOUT) == 1.0) { + float t = _cv_getFlag(_CV_FLAG_TRANSLUCENT_CUTOUT) == 1.0 ? _CV_TRANSLUCENT_CUTOUT_THRESHOLD : 0.5; + + if (a.a < t) { + discard; + } + } + + // PERF: varyings better here? + if (_cv_getFlag(_CV_FLAG_FLASH_OVERLAY) == 1.0) { + a = a * 0.25 + 0.75; + } else if (_cv_getFlag(_CV_FLAG_HURT_OVERLAY) == 1.0) { + a = vec4(0.25 + a.r * 0.75, a.g * 0.75, a.b * 0.75, a.a); + } + + // TODO: need a separate fog pass? + gl_FragData[TARGET_BASECOLOR] = _cv_fog(a); + gl_FragDepth = gl_FragCoord.z; + +#if TARGET_EMISSIVE > 0 + gl_FragData[TARGET_EMISSIVE] = vec4(bloom * a.a, 1.0, 0.0, 1.0); +#endif +} diff --git a/run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.vert b/run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.vert new file mode 100644 index 0000000..354c409 --- /dev/null +++ b/run/resourcepacks/lumi/assets/canvas/shaders/internal/material_main.vert @@ -0,0 +1,89 @@ +#include canvas:shaders/internal/header.glsl +#include frex:shaders/api/context.glsl +#include canvas:shaders/internal/varying.glsl +#include canvas:shaders/internal/vertex.glsl +#include canvas:shaders/internal/flags.glsl +#include frex:shaders/api/vertex.glsl +#include frex:shaders/api/sampler.glsl +#include canvas:shaders/internal/diffuse.glsl +#include canvas:shaders/internal/program.glsl +#include lumi:shaders/api/varying.glsl + +#include canvas:apitarget + +/****************************************************** + canvas:shaders/internal/material_main.vert +******************************************************/ + +void _cv_startVertex(inout frx_VertexData data, in int cv_programId) { +#include canvas:startvertex +} + +void _cv_endVertex(inout frx_VertexData data, in int cv_programId) { +#include canvas:endvertex +} + +void main() { + frx_VertexData data = frx_VertexData( + gl_Vertex, + in_uv, + in_color, + (in_normal_flags.xyz - 127.0) / 127.0, + in_lightmap.rg * 0.00390625 + 0.03125 + ); + + // Adding +0.5 prevents striping or other strangeness in flag-dependent rendering + // due to FP error on some cards/drivers. Also made varying attribute invariant (rolls eyes at OpenGL) + _cvv_flags = uint(in_normal_flags.w + 0.5); + + wwv_specPower = 0.0; + + _cv_setupProgram(); + + int cv_programId = _cv_vertexProgramId(); + _cv_startVertex(data, cv_programId); + + if (_cvu_atlas[_CV_SPRITE_INFO_TEXTURE_SIZE] != 0.0) { + float spriteIndex = in_material.x; + // for sprite atlas textures, convert from normalized (0-1) to interpolated coordinates + vec4 spriteBounds = texture2DLod(frxs_spriteInfo, vec2(0, spriteIndex / _cvu_atlas[_CV_SPRITE_INFO_TEXTURE_SIZE]), 0); + + float atlasHeight = _cvu_atlas[_CV_ATLAS_HEIGHT]; + float atlasWidth = _cvu_atlas[_CV_ATLAS_WIDTH]; + + // snap sprite bounds to integer coordinates to correct for floating point error + spriteBounds *= vec4(atlasWidth, atlasHeight, atlasWidth, atlasHeight); + spriteBounds += vec4(0.5, 0.5, 0.5, 0.5); + spriteBounds -= fract(spriteBounds); + spriteBounds /= vec4(atlasWidth, atlasHeight, atlasWidth, atlasHeight); + + data.spriteUV = spriteBounds.xy + data.spriteUV * spriteBounds.zw; + } + + data.spriteUV = _cv_textureCoord(data.spriteUV, 0); + + vec4 viewCoord = gl_ModelViewMatrix * data.vertex; + gl_ClipVertex = viewCoord; + gl_FogFragCoord = length(viewCoord.xyz); + + //data.normal *= gl_NormalMatrix; + data.vertex = gl_ModelViewProjectionMatrix * data.vertex; + + gl_Position = data.vertex; + + _cv_endVertex(data, cv_programId); + + _cvv_texcoord = data.spriteUV; + _cvv_color = data.color; + _cvv_normal = data.normal; + +#if DIFFUSE_SHADING_MODE != DIFFUSE_MODE_NONE + _cvv_diffuse = _cv_diffuse(_cvv_normal); +#endif + +#if AO_SHADING_MODE != AO_MODE_NONE + _cvv_ao = in_lightmap.b / 255.0; +#endif + + _cvv_lightcoord = data.light; +} diff --git a/run/resourcepacks/lumi/assets/canvas/shaders/internal/process/emissive_color.frag b/run/resourcepacks/lumi/assets/canvas/shaders/internal/process/emissive_color.frag new file mode 100644 index 0000000..510acb2 --- /dev/null +++ b/run/resourcepacks/lumi/assets/canvas/shaders/internal/process/emissive_color.frag @@ -0,0 +1,30 @@ +/* + * Derived from Canvas source code (https://github.com/grondag/canvas/) + * + * Changes are made to add bloom to sky fragments. + */ + +#include canvas:shaders/internal/process/header.glsl +#include frex:shaders/lib/color.glsl +#include frex:shaders/lib/sample.glsl +#include frex:shaders/lib/math.glsl + +/****************************************************** + canvas:shaders/internal/process/emissive_color.frag +******************************************************/ +uniform sampler2D _cvu_base; +uniform sampler2D _cvu_emissive; +uniform ivec2 _cvu_size; + +varying vec2 _cvv_texcoord; + +void main() { + vec4 e = texture2D(_cvu_emissive, _cvv_texcoord); + + bool sky = e.g == 0.0; + float bloom = sky ? 0.25 : e.r; + + vec4 c = frx_fromGamma(texture2D(_cvu_base, _cvv_texcoord)); + + gl_FragData[0] = vec4(c.rgb * bloom, e.r); +} diff --git a/run/resourcepacks/lumi/assets/lumi/materials/tasty_liquid.json b/run/resourcepacks/lumi/assets/lumi/materials/tasty_liquid.json new file mode 100644 index 0000000..3032be1 --- /dev/null +++ b/run/resourcepacks/lumi/assets/lumi/materials/tasty_liquid.json @@ -0,0 +1,7 @@ +{ + "blendMode": "translucent", + "disableAo": true, + "disableDiffuse": true, + "vertexSource": "lumi:shaders/material/water.vert", + "fragmentSource": "lumi:shaders/material/water.frag" +} diff --git a/run/resourcepacks/lumi/assets/lumi/shaders/api/varying.glsl b/run/resourcepacks/lumi/assets/lumi/shaders/api/varying.glsl new file mode 100644 index 0000000..62ec007 --- /dev/null +++ b/run/resourcepacks/lumi/assets/lumi/shaders/api/varying.glsl @@ -0,0 +1,2 @@ +// If wwv_specPower is used, frx_var0 must be reserved for fragPos and frx_var1 must be reserved for cameraPos +varying float wwv_specPower; diff --git a/run/resourcepacks/lumi/assets/lumi/shaders/lib/noise.glsl b/run/resourcepacks/lumi/assets/lumi/shaders/lib/noise.glsl new file mode 100644 index 0000000..e0a6833 --- /dev/null +++ b/run/resourcepacks/lumi/assets/lumi/shaders/lib/noise.glsl @@ -0,0 +1,7 @@ +#include frex:shaders/lib/noise/noise3d.glsl + +float l2_noise(vec3 aPos, float renderTime, float scale, float amplitude) +{ + float invScale = 1/scale; + return (snoise(vec3(aPos.x*invScale, aPos.z*invScale, renderTime)) * 0.5+0.5) * amplitude; +} diff --git a/run/resourcepacks/lumi/assets/lumi/shaders/material/water.frag b/run/resourcepacks/lumi/assets/lumi/shaders/material/water.frag new file mode 100644 index 0000000..c168b28 --- /dev/null +++ b/run/resourcepacks/lumi/assets/lumi/shaders/material/water.frag @@ -0,0 +1,38 @@ +#include lumi:shaders/api/varying.glsl +#include lumi:shaders/lib/noise.glsl +#include frex:shaders/api/fragment.glsl +#include frex:shaders/api/world.glsl +#include respackopts:config_supplier + +void frx_startFragment(inout frx_FragmentData fragData) { + fragData.spriteColor.rgb *= fragData.spriteColor.rgb * lumi_waterColorFactor; + + if(fragData.vertexNormal.y > 0.01) { + + // hack + fragData.light.y += 0.077 * smoothstep(1.0, 0.99, fragData.vertexNormal.y); + fragData.light.y = min(0.96875, fragData.light.y); + + vec3 worldPos = frx_modelOriginWorldPos() + frx_var0.xyz; + // water wavyness parameter + float timeScale = 2; // speed + float noiseScale = 2; // wavelength + float noiseAmp = 0.03125 * noiseScale;// * timeScale; // amplitude + + // inferred parameter + float renderTime = frx_renderSeconds() * 0.5 * timeScale; + float microSample = 0.01 * noiseScale; + + // base noise + float noise = l2_noise(worldPos, renderTime, noiseScale, noiseAmp); + + // normal recalculation + vec3 noiseOrigin = vec3(0, noise, 0); + vec3 noiseTangent = vec3(microSample, l2_noise(worldPos + vec3(microSample,0,0), renderTime, noiseScale, noiseAmp), 0) - noiseOrigin; + vec3 noiseBitangent = vec3(0, l2_noise(worldPos + vec3(0,0,microSample), renderTime, noiseScale, noiseAmp), microSample) - noiseOrigin; + + // noisy normal + vec3 noisyNormal = normalize(cross(noiseBitangent, noiseTangent)); + fragData.vertexNormal = noisyNormal; + } +} diff --git a/run/resourcepacks/lumi/assets/lumi/shaders/material/water.vert b/run/resourcepacks/lumi/assets/lumi/shaders/material/water.vert new file mode 100644 index 0000000..6332634 --- /dev/null +++ b/run/resourcepacks/lumi/assets/lumi/shaders/material/water.vert @@ -0,0 +1,8 @@ +#include frex:shaders/api/vertex.glsl +#include lumi:shaders/api/varying.glsl + +void frx_startVertex(inout frx_VertexData data) { + frx_var0.xyz = data.vertex.xyz; + frx_var1.xyz = (gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0)).xyz; + wwv_specPower = 100.0; +} diff --git a/run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/flowing_water.json b/run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/flowing_water.json new file mode 100644 index 0000000..077e5c9 --- /dev/null +++ b/run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/flowing_water.json @@ -0,0 +1,3 @@ +{ + "defaultMaterial": "lumi:tasty_liquid" +} diff --git a/run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/water.json b/run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/water.json new file mode 100644 index 0000000..077e5c9 --- /dev/null +++ b/run/resourcepacks/lumi/assets/minecraft/materialmaps/fluid/water.json @@ -0,0 +1,3 @@ +{ + "defaultMaterial": "lumi:tasty_liquid" +} diff --git a/run/resourcepacks/lumi/assets/respackopts/conf.json b/run/resourcepacks/lumi/assets/respackopts/conf.json new file mode 100644 index 0000000..c047af4 --- /dev/null +++ b/run/resourcepacks/lumi/assets/respackopts/conf.json @@ -0,0 +1,13 @@ +{ + "id": "lumi", + "version": 1, + "conf": { + "tonemap": [ + "reinhardJodie", + "vibrant", + "fxr" + ], + "sunColorFactor": 0.8, + "waterColorFactor": 1 + } +} diff --git a/run/resourcepacks/lumi/pack.mcmeta b/run/resourcepacks/lumi/pack.mcmeta new file mode 100644 index 0000000..779bb20 --- /dev/null +++ b/run/resourcepacks/lumi/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 6, + "description": "§3Aesthetic shader pack\n§8by awoo [v1.0-wip]" + } +} diff --git a/run/resourcepacks/lumi/pack.png b/run/resourcepacks/lumi/pack.png new file mode 100644 index 0000000000000000000000000000000000000000..e48cdab60329ac7814e4b198e65bf1caaa9d575a GIT binary patch literal 24229 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 if (!Respackopts.strVals.containsKey(screenId)) Respackopts.strVals.put(screenId, new HashMap<>()); + if (!Respackopts.enumKeys.containsKey(screenId)) + Respackopts.enumKeys.put(screenId, new HashMap<>()); String b = "respackopts.field." + screenId; for (Map.Entry entry : source.entrySet()) { String n = entry.getKey(); @@ -41,23 +46,19 @@ public class GuiFactory { Respackopts.boolVals.get(screenId).put(n, defaultValue); config.addEntry(entryBuilder.startBooleanToggle(getText(n, b), currentValue) .setDefaultValue(defaultValue) - .setSaveConsumer(v -> { - Respackopts.boolVals.get(screenId).put(n, v); - }) + .setSaveConsumer(v -> Respackopts.boolVals.get(screenId).put(n, v)) .build()); } else if (p.isNumber()) { double defaultValue = p.getAsDouble(); - double currentValue = defaultValue; + Double currentValue = defaultValue; if (Respackopts.numVals.get(screenId).containsKey(n)) currentValue = Respackopts.numVals.get(screenId).get(n); else Respackopts.numVals.get(screenId).put(n, defaultValue); config.addEntry(entryBuilder.startDoubleField(getText(n, b), currentValue) .setDefaultValue(defaultValue) - .setSaveConsumer(v -> { - Respackopts.numVals.get(screenId).put(n, v); - }) + .setSaveConsumer(v -> Respackopts.numVals.get(screenId).put(n, v)) .build()); } else if (p.isString()) { @@ -69,12 +70,34 @@ public class GuiFactory { Respackopts.strVals.get(screenId).put(n, defaultValue); config.addEntry(entryBuilder.startStrField(getText(n, b), currentValue) .setDefaultValue(defaultValue) - .setSaveConsumer(v -> { - Respackopts.strVals.get(screenId).put(n, v); - }) + .setSaveConsumer(v -> Respackopts.strVals.get(screenId).put(n, v)) .build()); } } + else if (e.isJsonArray()) { + TreeSet ev = Respackopts.enumKeys.get(screenId).get(n); + Double c = Respackopts.numVals.get(screenId).get(n); + String sel = ev.first(); + int i = 0; + for (String s1 : ev) { + if (c.intValue() == i) { + sel = s1; + } + i++; + } + config.addEntry(entryBuilder.startDropdownMenu(getText(n, b), (DropdownBoxEntry.SelectionTopCellElement) DropdownMenuBuilder.TopCellElementBuilder.of(sel, (s) -> s, (s) -> getText(s, n + "." + b)), new DropdownBoxEntry.DefaultSelectionCellCreator()) + .setSuggestionMode(false) + .setDefaultValue(ev.first()) + .setSelections(() -> ev.iterator()) + .setSaveConsumer(v -> { + int j = 0; + for (String s1 : ev) { + if (s1.equals(v)) + Respackopts.numVals.get(screenId).put(n, (double) j); + j++; + } + }).build()); + } else { System.err.println("[respackopts] Unsupported non-primitive datatype"); } @@ -88,11 +111,7 @@ public class GuiFactory { .setParentScreen(parent) .setTitle(getText(resourcepackid, "respackopts.title")); ConfigEntryBuilder entryBuilder = builder.entryBuilder(); - builder.setSavingRunnable(() -> { - System.out.println("Save"); - Respackopts.updateShaders(); - Respackopts.save(); - }); + builder.setSavingRunnable(Respackopts::save); ConfigCategory config = builder.getOrCreateCategory(getText(resourcepackid, "respackopts.category")); buildCategory(source, resourcepackid, config, entryBuilder); return builder.build(); diff --git a/src/main/java/io/gitlab/jfronny/respackopts/MMI.java b/src/main/java/io/gitlab/jfronny/respackopts/MMI.java index 9d0d90f..a12045c 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/MMI.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/MMI.java @@ -18,10 +18,7 @@ public class MMI implements ModMenuApi { .setParentScreen(parent) .setTitle(new TranslatableText("respackopts.mainconfig")); ConfigEntryBuilder entryBuilder = builder.entryBuilder(); - builder.setSavingRunnable(() -> { - Respackopts.updateShaders(); - Respackopts.save(); - }); + builder.setSavingRunnable(Respackopts::save); Respackopts.resPackMetas.forEach((s, v) -> { ConfigCategory config = builder.getOrCreateCategory(new TranslatableText("respackopts.category." + v.id)); Respackopts.factory.buildCategory(v.conf, v.id, config, entryBuilder); diff --git a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java index b0b518a..3eb1286 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java @@ -8,11 +8,13 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; +import javax.swing.*; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; +import java.util.TreeSet; import java.util.function.Consumer; @Environment(EnvType.CLIENT) @@ -20,19 +22,20 @@ public class Respackopts implements ClientModInitializer { public static HashMap> boolVals; public static HashMap> numVals; public static HashMap> strVals; + public static HashMap>> enumKeys; public static Gson g = new Gson(); public static GuiFactory factory = new GuiFactory(); public static final Integer metaVersion = 1; public static HashMap resPackMetas = new HashMap<>(); public static final String ID = "respackopts"; static final Path p = FabricLoader.getInstance().getConfigDir().resolve("respackopts"); - public static final HashSet> shaderConsumers = new HashSet<>(); + public static final HashSet saveActions = new HashSet<>(); @Override public void onInitializeClient() { load(); - Respackopts.updateShaders(); + Respackopts.save(); if (FabricLoader.getInstance().isDevelopmentEnvironment()) - shaderConsumers.add(System.out::println); + saveActions.add(() -> System.out.println("Save")); } public static void save() { @@ -61,6 +64,9 @@ public class Respackopts implements ClientModInitializer { e.printStackTrace(); } } + for (Runnable action : saveActions) { + action.run(); + } } public static void load() { @@ -90,37 +96,14 @@ public class Respackopts implements ClientModInitializer { deNull(); } - public static void deNull() { + private static void deNull() { if (boolVals == null) boolVals = new HashMap<>(); if (numVals == null) numVals = new HashMap<>(); if (strVals == null) strVals = new HashMap<>(); - } - - public static void updateShaders() { - StringBuilder sb = new StringBuilder(); - numVals.forEach((s, v) -> v.forEach((s1, v1) -> { - sb.append("\n"); - sb.append("#define "); - sb.append(s); - sb.append("_"); - sb.append(s1); - sb.append(" "); - sb.append(v1); - })); - boolVals.forEach((s, v) -> v.forEach((s1, v1) -> { - if (v1) { - sb.append("\n"); - sb.append("#define "); - sb.append(s); - sb.append("_"); - sb.append(s1); - } - })); - for (Consumer consumer : shaderConsumers) { - consumer.accept(sb.toString()); - } + if (enumKeys == null) + enumKeys = new HashMap<>(); } } diff --git a/src/main/java/io/gitlab/jfronny/respackopts/integration/FrexCompat.java b/src/main/java/io/gitlab/jfronny/respackopts/integration/FrexCompat.java index f926db0..dea19c7 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/integration/FrexCompat.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/integration/FrexCompat.java @@ -6,14 +6,52 @@ import io.gitlab.jfronny.respackopts.Respackopts; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.util.Identifier; +import java.util.concurrent.atomic.AtomicInteger; + public class FrexCompat implements FrexInitializer { - String currentShaderCode = ""; @Override public void onInitalizeFrex() { - System.out.println("[respackopts] enabling canvas support"); - ShaderConfig.registerShaderConfigSupplier(new Identifier(Respackopts.ID, "config_supplier"), () -> currentShaderCode); - Respackopts.shaderConsumers.add((s) -> { - currentShaderCode = s; + ShaderConfig.registerShaderConfigSupplier(new Identifier(Respackopts.ID, "config_supplier"), () -> { + StringBuilder sb = new StringBuilder(); + Respackopts.numVals.forEach((s, v) -> v.forEach((s1, v1) -> { + sb.append("\n"); + sb.append("#define "); + sb.append(s); + sb.append("_"); + sb.append(s1); + sb.append(" "); + String tmp = v1.toString(); + if (tmp.endsWith(".0")) + tmp = tmp.substring(0, tmp.length() - 2); + sb.append(tmp); + })); + Respackopts.boolVals.forEach((s, v) -> v.forEach((s1, v1) -> { + if (v1) { + sb.append("\n"); + sb.append("#define "); + sb.append(s); + sb.append("_"); + sb.append(s1); + } + })); + Respackopts.enumKeys.forEach((s, v) -> v.forEach((s1, v1) -> { + AtomicInteger i = new AtomicInteger(0); + v1.forEach((s2) -> { + sb.append("\n"); + sb.append("#define "); + sb.append(s); + sb.append("_"); + sb.append(s1); + sb.append("_"); + sb.append(s2); + sb.append(" "); + sb.append(i.getAndIncrement()); + }); + })); + return sb.toString(); + }); + System.out.println("[respackopts] enabled frex/canvas support"); + Respackopts.saveActions.add(() -> { try { ShaderConfig.invalidateShaderConfig(); } diff --git a/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java b/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java index 8dbbd94..99bdab8 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java @@ -1,9 +1,6 @@ package io.gitlab.jfronny.respackopts.mixin; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; +import com.google.gson.*; import io.gitlab.jfronny.respackopts.Respackopts; import io.gitlab.jfronny.respackopts.data.Respackmeta; import net.minecraft.resource.ResourcePackManager; @@ -20,7 +17,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.TreeSet; @Mixin(ResourcePackManager.class) public class ResourcePackManagerMixin { @@ -38,9 +37,11 @@ public class ResourcePackManagerMixin { Respackopts.numVals.put(conf.id, new HashMap<>()); if (!Respackopts.strVals.containsKey(conf.id)) Respackopts.strVals.put(conf.id, new HashMap<>()); + if (!Respackopts.enumKeys.containsKey(conf.id)) + Respackopts.enumKeys.put(conf.id, new HashMap<>()); for (Map.Entry entry : conf.conf.entrySet()) { String n = entry.getKey(); - if (n.contains(":") || n.contains(".")) { + if (n.contains(":") || n.contains(".") || n.contains("_")) { System.err.println(n + " contains invalid characters"); continue; } @@ -60,6 +61,32 @@ public class ResourcePackManagerMixin { Respackopts.strVals.get(conf.id).put(n, p.getAsString()); } } + else if (e.isJsonArray()) { + JsonArray a = e.getAsJsonArray(); + for (JsonElement element : a) { + if (!element.isJsonPrimitive()) { + System.err.println("[respackopts] Unsupported non-primitive datatype"); + continue; + } + if (!Respackopts.enumKeys.get(conf.id).containsKey(n)) { + Respackopts.enumKeys.get(conf.id).put(n, new TreeSet<>()); + } + JsonPrimitive p = element.getAsJsonPrimitive(); + if (!p.isString()) { + System.err.println("[respackopts] Unsupported non-string enum key"); + continue; + } + String b = p.getAsString(); + if (b.contains(":") || b.contains(".") || b.contains("_")) { + System.err.println(b + " contains invalid characters"); + continue; + } + Respackopts.enumKeys.get(conf.id).get(n).add(b); + } + if (!Respackopts.numVals.get(conf.id).containsKey(n)) { + Respackopts.numVals.get(conf.id).put(n, 0d); + } + } else { System.err.println("[respackopts] Unsupported non-primitive datatype"); } @@ -75,9 +102,8 @@ public class ResourcePackManagerMixin { } } }); - Respackopts.load(); - Respackopts.updateShaders(); Respackopts.save(); + Respackopts.load(); } private boolean hasMetadata(ResourcePackProfile v, String fname) { diff --git a/src/main/resources/assets/respackopts/lang/en_us.json b/src/main/resources/assets/respackopts/lang/en_us.json index 3431a25..96e80b0 100644 --- a/src/main/resources/assets/respackopts/lang/en_us.json +++ b/src/main/resources/assets/respackopts/lang/en_us.json @@ -2,5 +2,6 @@ "respackopts.loadFailed": "Failed to load resourcepacks", "respackopts.loadError": "There is an issue with the resourcepack configs. Please take a look at the game log", "respackopts.configure": "Configure", - "respackopts.mainconfig": "ResPack Opts" + "respackopts.mainconfig": "ResPack Opts", + "respackopts.invalid": "Invalider Wert" } \ No newline at end of file