Skip to content

Commit

Permalink
Add missing hatch shader, improve watercolor shader
Browse files Browse the repository at this point in the history
  • Loading branch information
jhaakma committed Jun 21, 2024
1 parent f328345 commit e1c3a27
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 6 deletions.
6 changes: 5 additions & 1 deletion Data Files/MWSE/mods/mer/joyOfPainting/interops/artStyle.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ local shaders = {
id = "hatch",
shaderId = "jop_hatch",
defaultControls = { "hatchSize" }
},
{
id = "mottle",
shaderId = "jop_mottle",
}
}

Expand Down Expand Up @@ -456,7 +460,7 @@ Use the detail setting to adjust how dense the lines are, and the fog setting to
shaders = {
"detail",
"watercolor",
"splash",
"mottle",
"distort",
"adjuster",
"fogColor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local common = require("mer.joyOfPainting.common")
local logger = common.createLogger("ZoomSlider")
local config = require("mer.joyOfPainting.config")

local useMGEZoom = false
local useMGEZoom = true

---@class JOP.PhotoMenu.ZoomSlider
local ZoomSlider = {}
Expand All @@ -13,7 +13,7 @@ function ZoomSlider:new(photoMenu)
local o = {}
setmetatable(o, self)
self.photoMenu = photoMenu
self.camera = tes3.worldController.worldCamera.cameraData
--self.camera = tes3.worldController.worldCamera.cameraData
self.__index = self
return o
end
Expand Down Expand Up @@ -43,7 +43,7 @@ end

function ZoomSlider:updateZoom()
if useMGEZoom then
mge.camera.zoom = config.persistent.zoom
mge.camera.zoom = config.persistent.zoom / 100
else
local zoom = (config.persistent.zoom / 100)
local x = math.tan((math.pi / 360) * mge.camera.fov)
Expand Down Expand Up @@ -74,7 +74,9 @@ end
function ZoomSlider:restore()
mge.render.pauseRenderingInMenus = self.previousPauseRenderingInMenus
mge.camera.zoomEnable = self.previousZoomState
if not useMGEZoom then
if useMGEZoom then
mge.camera.zoom = 1
else
self.camera.fov = self.previousFov
end
end
Expand Down
Binary file modified Data Files/Textures/jop/Hatch1.tga
Binary file not shown.
Binary file modified Data Files/Textures/jop/Hatch2.tga
Binary file not shown.
Binary file modified Data Files/Textures/jop/composite_tex.dds
Binary file not shown.
Binary file modified Data Files/Textures/jop/luts/saturated.tga
Binary file not shown.
Binary file removed Data Files/Textures/jop/packedHatch.tga
Binary file not shown.
158 changes: 158 additions & 0 deletions Data Files/shaders/XEShaders/jop_hatch.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
extern float hatchStrength = 3.0;
extern float hatchSize = 0.15;

#define PI 3.1415926535897932384626433832795

float3 eyepos, eyevec;
float2 rcpres;
float fov;
float waterlevel;

matrix mview;
matrix mproj;

texture lastshader;
texture lastpass;
texture depthframe;
texture tex1 < string src="jop/Hatch1.tga"; >;
texture tex2 < string src="jop/Hatch2.tga"; >;

sampler sLastShader = sampler_state { texture = <lastshader>; addressu = mirror; addressv = mirror; magfilter = linear; minfilter = linear; };
sampler sDepthFrame = sampler_state { texture = <depthframe>; addressu = wrap; addressv = wrap; magfilter = point; minfilter = point; };
sampler sLastPass = sampler_state { texture = <lastpass>; addressu = clamp; addressv = clamp; magfilter = linear; minfilter = linear; };
sampler sHatch1 = sampler_state { texture = <tex1>; addressu = wrap; addressv = wrap; magfilter = linear; minfilter = linear; };
sampler sHatch2 = sampler_state { texture = <tex2>; addressu = wrap; addressv = wrap; magfilter = linear; minfilter = linear; };

static const float2 invproj = 2.0 * tan(0.5 * radians(fov)) * float2(1, rcpres.x / rcpres.y);
static const float xylength = sqrt(1 - eyevec.z * eyevec.z);
static const float sky = 1e6;

float readDepth(in float2 coord : TEXCOORD0)
{
float posZ = tex2D(sDepthFrame, coord).x;
return posZ;
}

float4 sample0(sampler2D s, float2 t)
{
return tex2Dlod(s, float4(t, 0, 0));
}

float3 toView(float2 tex)
{
float depth = sample0(sDepthFrame, tex).r;
float2 xy = depth * (tex - 0.5) * invproj;
return float3(xy, depth);
}


float getNormal(in float2 tex : TEXCOORD0)
{
float3 pos = toView(tex);
float water = pos.z * eyevec.z - pos.y * xylength + eyepos.z;

if(pos.z <= 0 || pos.z > sky || (water - waterlevel) < 0)
return float4(0.5, 0.5, 1, 1);

float3 left = pos - toView(tex + rcpres * float2(-1, 0));
float3 right = toView(tex + rcpres * float2(1, 0)) - pos;
float3 up = pos - toView(tex + rcpres * float2(0, -1));
float3 down = toView(tex + rcpres * float2(0, 1)) - pos;

float3 dx = length(left) < length(right) ? left : right;
float3 dy = length(up) < length(down) ? up : down;

float3 normal = normalize(cross(dy, dx));

return normal;
}

/***********************************************************
* Hatch shader
* The hatch texture is 6 levels of hatching encoded
* In the RGB of two images side by side
***********************************************************/


float3 Hatching(float2 _uv, half _intensity)
{

float strength = saturate(_intensity * hatchStrength);


//rotate uv by 45 degrees
float2 uv = sin(PI/4) * _uv + cos(PI/4) * _uv;
half3 hatch1 = tex2D(sHatch1, uv / hatchSize).rgb;
half3 hatch0 = tex2D(sHatch2, uv / hatchSize).rgb;

half3 overbright = max(0, strength - 1.0);

half3 weightsA = saturate((strength * 6.0) + half3(-0, -1, -2));
half3 weightsB = saturate((strength * 6.0) + half3(-3, -4, -5));

weightsA.xy -= weightsA.yz;
weightsA.z -= weightsB.x;
weightsB.xy -= weightsB.yz;

hatch0 = hatch0 * weightsA;
hatch1 = hatch1 * weightsB;

half3 hatching = overbright + hatch0.r +
hatch0.g + hatch0.b +
hatch1.r + hatch1.g +
hatch1.b;

return hatching;
}


float2 rotateUvByNormal(float2 uv, float3 normal)
{
// Step 1: Calculate rotation axis and angle
float3 upVector = float3(0, 1, 0);
float3 rotationAxis = cross(upVector, normal);
float angle = acos(dot(normalize(upVector), normalize(normal)));

// Step 2: Create rotation matrix
float c = cos(angle);
float s = sin(angle);
float t = 1.0 - c;
float x = rotationAxis.x, y = rotationAxis.y, z = rotationAxis.z;
float3x3 rotationMatrix = float3x3(
t*x*x + c, t*x*y - s*z, t*x*z + s*y,
t*x*y + s*z, t*y*y + c, t*y*z - s*x,
t*x*z - s*y, t*y*z + s*x, t*z*z + c
);

// Step 3: Apply rotation to the hatch pattern
float2 rotatedHatchPosition = mul(rotationMatrix, uv);
return rotatedHatchPosition;
}

float4 hatch(float2 tex : TEXCOORD0) : COLOR0
{

float3 color = tex2D(sLastShader, tex).rgb;

float3 normal = getNormal(tex);

// Adjust UV coordinates based on the normal
float2 adjustedUV = tex;
//Rotate the hatch texture according the normal
adjustedUV = rotateUvByNormal(adjustedUV, normal);

// Get luminosity
float luminosity = dot(color, float3(0.299, 0.587, 0.114));

// Use adjusted UV for hatching
float3 hatching = Hatching(adjustedUV, luminosity);

return float4(hatching, 1);
}



technique T0 < string MGEinterface = "MGE XE 0"; string category = "final"; int priorityAdjust = 75;>
{
pass a { PixelShader = compile ps_3_0 hatch(); }
}
73 changes: 73 additions & 0 deletions Data Files/shaders/XEShaders/jop_mottle.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
extern float mottleStrength = 0.03;
extern float mottleSize = 1.0;
extern float sampleSpeed = 0.05;

float time;

texture lastshader;
texture tex1 < string src="jop/waterbrush.dds"; >;

sampler sLastShader = sampler_state { texture = <lastshader>; addressu = mirror; addressv = mirror; magfilter = linear; minfilter = linear; };
sampler sMottle = sampler_state { texture = <tex1>; addressu = wrap; addressv = wrap; magfilter = linear; minfilter = linear; };


// Step 3 & 4: Apply Mottling Effect and Sample Mottle Texture
float3 applyMottlingEffect(float2 uv, float3 baseColor)
{
float3 newColor = baseColor; // Initialize new color

//Darken
float2 randomUv1 = float2(uv.x + sin(time) * sampleSpeed, uv.y + cos(time) * sampleSpeed);
float3 mottleColor1 = tex2D(sMottle, randomUv1 / mottleSize); // Sample mottle texture
newColor = newColor + lerp(-mottleStrength, 0, mottleColor1.r);

//Lighten
float time2 = time * 0.5;
float2 randomUv2 = float2(uv.x + sin(time2) * sampleSpeed, uv.y + cos(time2) * sampleSpeed);
float3 mottleColor2 = tex2D(sMottle, randomUv2 / mottleSize); // Sample mottle texture
newColor = newColor + lerp(0, mottleStrength, mottleColor2.g);

return newColor; // Blend base color with mottled color
}


float2 rotateUvByNormal(float2 uv, float3 normal)
{
// Step 1: Calculate rotation axis and angle
float3 upVector = float3(0, 1, 0);
float3 rotationAxis = cross(upVector, normal);
float angle = acos(dot(normalize(upVector), normalize(normal)));

// Step 2: Create rotation matrix
float c = cos(angle);
float s = sin(angle);
float t = 1.0 - c;
float x = rotationAxis.x, y = rotationAxis.y, z = rotationAxis.z;
float3x3 rotationMatrix = float3x3(
t*x*x + c, t*x*y - s*z, t*x*z + s*y,
t*x*y + s*z, t*y*y + c, t*y*z - s*x,
t*x*z - s*y, t*y*z + s*x, t*z*z + c
);

// Step 3: Apply rotation to the hatch pattern
float2 rotatedHatchPosition = mul(rotationMatrix, uv);
return rotatedHatchPosition;
}

float4 main(float2 tex : TEXCOORD0) : COLOR0
{

float3 color = tex2D(sLastShader, tex).rgb;

// apply mottling
float3 mottledColor = applyMottlingEffect(tex, color);

return float4(mottledColor, 1);
}



technique T0 < string MGEinterface = "MGE XE 0"; string category = "final"; int priorityAdjust = 150;>
{
pass a { PixelShader = compile ps_3_0 main(); }
}
2 changes: 1 addition & 1 deletion Data Files/shaders/XEShaders/jop_splash.fx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sampler sOverlayImage = sampler_state { texture=<tex1>; minfilter = linear; magf
float4 main(float2 Tex : TEXCOORD0) : COLOR0
{
// Sample the scroll texture
float2 scrollUV = float2(Tex.x + sin(time) * 0.1, Tex.y + cos(time) * 0.1);
float2 scrollUV = float2(Tex.x + sin(time) * 0.05, Tex.y + cos(time) * 0.05);
float4 scrollTex = tex2D(sOverlayImage, scrollUV);

// Calculate the brightness of the scroll texture
Expand Down

0 comments on commit e1c3a27

Please sign in to comment.
  翻译: