From 720d58533cf56793395b28cd86d49b4a97ee2c15 Mon Sep 17 00:00:00 2001 From: luisangelsm Date: Mon, 19 Jan 2026 19:23:57 +0100 Subject: [PATCH] Fix the fragment shader to work with any background color There now some dither to avoid banding in the gradients. --- common/rhi/shaders/flow.frag | 16 +++++++--- common/rhi/shaders/flow.frag.qsb | Bin 2682 -> 3176 bytes common/rhi/yacreader_comic_flow_rhi.cpp | 4 +-- common/rhi/yacreader_flow_rhi.cpp | 38 ++++++++++++++++-------- common/rhi/yacreader_flow_rhi.h | 12 ++++---- common/rhi/yacreader_page_flow_rhi.cpp | 4 +-- 6 files changed, 47 insertions(+), 27 deletions(-) diff --git a/common/rhi/shaders/flow.frag b/common/rhi/shaders/flow.frag index 5e6f6c40..411204f4 100644 --- a/common/rhi/shaders/flow.frag +++ b/common/rhi/shaders/flow.frag @@ -51,11 +51,19 @@ void main() // For reflections, apply a gradient fade using reflection uniforms (darker further away) if (vIsReflection > 0.5) { - // Legacy reflection fade: ramp from 0.0 to 0.33 using texture V coordinate - float gradientFade = mix(0.0, 0.33, vTexCoord.y); + float gradientFade = mix(reflectionUp, reflectionDown, vTexCoord.y); shadingAmount *= gradientFade; } - // Final color: shaded RGB, keep source alpha - fragColor = vec4(texColor.rgb * shadingAmount, texColor.a); + // Subtle dithering to break up bands (very low amplitude, within 8-bit quantization) + float rnd = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898,78.233))) * 43758.5453); + // scale to roughly +/- 0.5/255 range (adjust strength if needed) + float dither = (rnd - 0.5) / 255.0; + + float shadedAmountDithered = clamp(shadingAmount + dither, 0.0, 1.0); + + fragColor = vec4( + mix(backgroundColor, texColor.rgb, shadedAmountDithered), + texColor.a + ); } diff --git a/common/rhi/shaders/flow.frag.qsb b/common/rhi/shaders/flow.frag.qsb index 02f9d8a6c07cb8e469453d7f1acaa3e452d97988..cf44ec8e09e01c919c5dbf0718a69effc82d7d90 100644 GIT binary patch literal 3176 zcmV-u443l&05|-2ob6ldcN5nUzkY*&kOv_luP%^K2^1mA7B(20haDcp)CSu?D_pUb zcI^d8tL(098E^wl(j+bY*3(b@OZrdrTmOuH?d$ZkGxyG2-Mv}~)02>MQg6A54`BubF2=}B^Wzkn>a2S>Sp8}Zp3Fd zLD`1UEl>_X-35a}y|0`4k(fE$1ZAY6X8OCC>EThZA?hZC3|FAS*FXfX>k9O6CzfCw z2MnN6m0e^Fc(!3KXkw5R**F`6r@Eqti=TwqJAm4NpnITuKjQv%?t=$Z6R>+o@4RB; zRr&A%Tj61U0JBF!4Nxyf{OBXqy>UJyV1{*`Gg!uPOU?03X{4fa18*aIr~dwF(Y zHsc57%xE5S1R~O>46=_8f_K$)YC@l}x8Obw_Z7IW!EM9+8182bQ@4-fhtM`W?StRL zWKYsZXf8;jy$?dVkK2BQWN(p7>K-S0fUs8->=lyrlI)ryyGF7;k}VPL3dojd>;X;> zg!B*-+fd~lrC-VGA>Mg~*Gs%vh1W;Cd4<Bp-FX3NhtxYmP^lQXJ`2m{CAkha& zHbnS?OyoH7-URw>@(r&OL?0p=_1_`vpdy!fE9oONKS>{BA}{3pjw^Vy^RY00PSPip z@sZ}6l|4B(LE~Q`eMw&<`T&f2o#Cm}4`-n8v7h3$Ky&URKNrc*9zOp)Jg-nk;^eQq z7nK;^BL9|X4oKftd`9dYCUP9{KO@oY0e8uoS%{W6wINQwGL&p z@ps@15El2qZe^?kJmyjQeT_b>(I07aN~3ccJ*LrTHTr@^U()Cs8f|Ixry9LZ^d!)@ zSMmJVM>MX_>rCv!+YlARy+Ih{PeH_X6Ym1ZF}CkAu@4WC4P3wXh>!R;iI(^u5&i=O zZxA2x?-MQYM+tvK!GB16#2+SF;(tu|V+uY=e8hi5w8WoaEUZ})pC1EEDFgg82pUWgZn73wtq% zxkr2%A5)1>fo$9(|89_6>aH+xen`5+#Q7rQQzqVu5+CIK1N@VI&XYc#75Kty^EeFg zvt*5Us}v`k*YC-8h2#(T+y^)-`Wuh+4fqj(!ig-_zxb`Y@{G5q%ejX^qe?dHX?oc8dp6_ zzast(^8ahH!xY{(ifzgJ4e@&UyoR`6za{-|Nd6VY=rPIu4Hm{l$JtNsHu5c!+R(wN zqwALEJ8sPw&Sn#J&nh{bT60#{>V{o$&0wO9?bLvo@B^<=3XG+iW4m7UV#BsAFYzd0 zz*RMaQDfb)Hs(F|zEui9^|~2&PIH2b*R9fsv0|1Uta@&vR-Se%t|ugXK%-bU%UK2L zubE}1wi=5YRvDgUSHy5j^?3e_yHOJoaKdo>o1sKEoSR5&ftL;8UeryaI6kJxL{xmU zTCZ4MZpJ9N5Y$Dh88kc#VC$y03DZYka>hFFr(M@8PbBOL45bJmWLIl`VAe|3g7A1k z)17roW@X;>HC-|3PJiYCLL zXEu!!MmCdA!ynnSF`UUpd}_vMG`nh`do(!cR-I;2I-I5mrH##0+x(*{p-X8Q1zpK+ z8-gsPBlglpGfwS76}D&q%F@j7m_+0I$u^Ra>@ud%_Y@b3q-hyn^}v`_3oe;uizWtJ zDx>b-v=QY9AH#ggNGI`%&h_|Y#J+%SsbQ>@VNlyMOF`0yq%ONbax=vyffJ zT!-D7ltI=G-gLN)!R?3K?~!EOEK(^2x&cFNB@$X?X|nJd+}o-wx$I80Xvuc!#7>>q zsS`VO;{Ui#{GnAR2L9Em6ZxGwv7I{M)Z$Bo;Kx;mP(v#W!0%cU6nVraw-Jb;Y`ztE z06Q4`U#i1SZP=*|JGJ5atqtMMeo18kziZ`MWnoXUv?310=n;7aPTf_<4=gXal9k7Z z=WZ0M=6#qe_kXlGitApCD-~b>67GR(+rAYfOOeeq!nHL2G=BNiVoubX?E3e zqv7*$Mu&A!)F_9kxvs%abpi9!OM{1d0V^27)hgBoei%37TpNrpGo1X3vg}q0@@K# z-iiMz<3ah(_Ghr&T#W01m!F_@ZojaSn@1)TmNZIZUr88oI5}a;5Ox*3hl88?h~`P6ykO zeH7h8x<=dkdV8_yFi5L9%fz7bUNBeSnr`D-WD-;?& zw6PVpR1><2`L>OufQM6foSH#IrG0xS8o3S{>fTV`BXMxGzf!RI&})oH6>3B)V&W+& z>Gg$Yg@8y38Q?qTG zpTk_eHjBOw11@S*+_$lm-u@AETc5|2lJAO2kEMQqA3*fIO#5ag{>2?$G5-Qb>kYbM O8PU29zy1q$U~MWG5-gJ70H8!Ub9!VXxs zK{y0)4-^XVfnMTQ66UZ5!bru{4E9pf$D`mt)J+H(u0V~ife2pD73kwmY=RvZ6rfO@ z-DfR$c3>{3;tZ>>A}g?Icy20^xcpg|zYDlS2)hr`FJb|B#J%u=Y6JEx$=y|SyeS_p zu^k@xeV9KgYJqq)=E(qwzLw-g2Iko0SyLdNmZkIh^*#h!O)%ZY>i63yl8tBSHvEKq z*yQ0u!RKj$A0-Mdz~^C<3DtC~ZfYjJ4)rZ z0Et2Q*4InPaj#+et}0V*5A=_Xp#HOvpf%#Y1-l+Co&^#7{(O0TIUj`8{8Sn90-`Vw z&HfEonGc7o_3!Y(SX*3Z&l$967QM98zSCwg*QyRs=^x~-k!ocL%bUKyiaSy z6k4v)J21`R>ozXO8N@TPtu>#+RFJ&D0tNKbD{o@q^~LVk>(qd{rb)f*8HfQxNeb81BzaU$tpI;j~OGh&KRdXic=r=|18ff#7I5z zTi$!h8oZ$RxMU0Ied1k*Jnd(l(u(u>3vh-Ai}^LG_;H@kCr*E?(U&#)6OA6%=#oZH zYxHf6zNgU-H2N{ovp{2BW4$>?H0JHwOyukr;3eYTAq?{8AOfSry9eXAE*~bb<{i2n^>kj@hi_r`0m>d2FaimNn^`=mfN#%WyIm?Rr&jHZY$ZIp?p+PJRRC~G!K zG%oespj?ymP0B?{-=f@-x@Smto@8$l26a!2&pDDWU6Mlho-=lFU`#BT)LDEZ1)T`f6%zr_cC6a$Y zJc-ACi1qCEfX7<)OXA@g{TcAs8}a%Z;Bjn)iGGPSVntd1b;hFeh19)4eCgMw;uo&- z8ewo=k7-=WK4D@XOZp2Y_PO-yOX59I{6gMeVP0~6t0a$m9Q*kwpZ^(NE4PXFjQr!T zRf@f@$lkYL5AhX}`HFnTI`f=#s{7C+{$rZw4#_fwXA!SY;Z=#(&*MA7=WLU_MdK#< z`jp1L0}VGsXCI{VqCAaLyC1qXc3}H~>otsGp^$0%w&ika&)wZ?8cxkK!q578k-H*@%;MzmgCrd=Ie|BSKSP!j04v`T=l&dwiSZtBQx~f_6#3Bu&qgB$F%l$ zeXrH1&U-b_7bD!DQE8ghf&vZp%&ObiO~e&dhHpDHVQ#aT%wO;h8)5{UFx+4*8qpP( zW-_n9%a(92?xs;G7N(U!5thKLH*2wN)?BWW}r_;gzOox#3X?DCTo7HEUjI3RLF6 zbE^iXPfy@9~X&S%E>6`LP3`a(vXYk%Ngw?v3qscqag@OH4_Pm#y6B>WMkQ7%%Sh& zd{87!NBOP~%Irq?z^vLdF&?5qesZ#aS8=jfoGg@zH_C9UebdFdct@p(?n z#&iqVEB?7#t32?{-32o=w+h>s0g%I4nZx<^(M#|xrY#p`c>WM9zRF~@Qrc$GHTYep zl$HvojCJKI1IgBK2Wi!~e}<80GsvT?g@1z0|3Lqu*FNq6)-o!YwE>yKWHL zes-rIw~g-|R_f*pm<5l-RC|ioj!IH-1Pu`3ea~?MJIq?K?3ol|TG7-breZ6~1+bFp zDDI-JS*@{}KsixuQAfqq3Y=PyHT?BnXufGRe3S-Qxj9}f-(BdJiL)+s;rMz($=i_U zyW3)==3=#6UEL5@W?hv0btsv(QBk24>ISMvfFjT_p}tyTMzNlt4kR@NO|a7%)PaPJ zstv~)L)@m)9g=k_&4C*K`s&i!lghmB1wm!0(QJj3^16EvhmFs^oL^a4TcD=i@L&mT zzvd#_Nf(cd{3qMEbF4njrzRrRqmw)T(pD}K{DjTCd!vcNZC431AVj-%#l`37zC)Nc;ctCxoED(YM)su3ExQ{rQ~Wra)89b4M?vg#6DZ$90HEyYNu6Ueop5+oQ2 zu@-7PP6&|7$;Xzr(}(!+$%YEm&=kB<4`+ic_BT_*Yh!N1sBfmEX?{EPwR>Q2@0YuL>xK!`|7Kx5>>94qSv^w5PE`k4>ueszc2DN~J zPHRM4TNZw23*+0{36gFj?GUiF)e7JsRP(HckX6)oZ5;(%9M2i}z8Vwh-VlmJDMdov z4GMfR39fc;1zV21#)#CRRCOXIp0Y9y_u__Lj+WB&8OTJNKcyjV5 zg&1fZShxf<#*yREPcCvEc+VPNzlw!8?LZ(@WxK58VE*~xJQB{Y|5!137UE^ zNCJ1A4IDEFZ`~5j9tBSDik~X_<9ydw!`XFC;Di6pH-0tce%MLD)7#03PYX)d@kF!1 oZZpKU7yTBX{T?9x)EuvbKRngiQ`euRC;j=JF2k>X0FS3dKKxc${{R30 diff --git a/common/rhi/yacreader_comic_flow_rhi.cpp b/common/rhi/yacreader_comic_flow_rhi.cpp index 6117114e..a5ec18fe 100644 --- a/common/rhi/yacreader_comic_flow_rhi.cpp +++ b/common/rhi/yacreader_comic_flow_rhi.cpp @@ -37,7 +37,7 @@ void YACReaderComicFlow3D::updateImageData() // Create QRhiTexture from the loaded image if (m_rhi) { - QRhiTexture *texture = m_rhi->newTexture(QRhiTexture::BGRA8, img.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); + QRhiTexture *texture = m_rhi->newTexture(QRhiTexture::RGBA8, img.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); if (texture->create()) { PendingTextureUpload upload; @@ -168,7 +168,7 @@ QImage ImageLoader3D::loadImage(const QString &fileName) break; } - return image; + return image.convertToFormat(QImage::Format_RGBA8888); } ImageLoader3D::ImageLoader3D(YACReaderFlow3D *flow) diff --git a/common/rhi/yacreader_flow_rhi.cpp b/common/rhi/yacreader_flow_rhi.cpp index d3b7b715..f3c0a33c 100644 --- a/common/rhi/yacreader_flow_rhi.cpp +++ b/common/rhi/yacreader_flow_rhi.cpp @@ -37,8 +37,8 @@ YACReaderFlow3D::YACReaderFlow3D(QWidget *parent, struct Preset p) shadingTop = 0.8f; shadingBottom = 0.02f; - reflectionUp = 0.f; - reflectionBottom = 0.6f; + reflectionUp = 0.0f; + reflectionBottom = 0.33f; setBackgroundColor(Qt::black); @@ -119,9 +119,9 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb) // Initialize default texture from image if (!scene.defaultTexture) { - QImage defaultImage(":/images/defaultCover.png"); + QImage defaultImage = QImage(":/images/defaultCover.png").convertToFormat(QImage::Format_RGBA8888); - scene.defaultTexture.reset(m_rhi->newTexture(QRhiTexture::BGRA8, defaultImage.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); + scene.defaultTexture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, defaultImage.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); scene.defaultTexture->create(); getResourceBatch()->uploadTexture(scene.defaultTexture.get(), defaultImage); getResourceBatch()->generateMips(scene.defaultTexture.get()); @@ -131,9 +131,9 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb) #ifdef YACREADER_LIBRARY // Initialize mark textures if (!scene.markTexture) { - QImage markImage(":/images/readRibbon.png"); + QImage markImage = QImage(":/images/readRibbon.png").convertToFormat(QImage::Format_RGBA8888); if (!markImage.isNull()) { - scene.markTexture.reset(m_rhi->newTexture(QRhiTexture::BGRA8, markImage.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); + scene.markTexture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, markImage.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); scene.markTexture->create(); getResourceBatch()->uploadTexture(scene.markTexture.get(), markImage); getResourceBatch()->generateMips(scene.markTexture.get()); @@ -141,9 +141,9 @@ void YACReaderFlow3D::initialize(QRhiCommandBuffer *cb) } if (!scene.readingTexture) { - QImage readingImage(":/images/readingRibbon.png"); + QImage readingImage = QImage(":/images/readingRibbon.png").convertToFormat(QImage::Format_RGBA8888); if (!readingImage.isNull()) { - scene.readingTexture.reset(m_rhi->newTexture(QRhiTexture::BGRA8, readingImage.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); + scene.readingTexture.reset(m_rhi->newTexture(QRhiTexture::RGBA8, readingImage.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips)); scene.readingTexture->create(); getResourceBatch()->uploadTexture(scene.readingTexture.get(), readingImage); getResourceBatch()->generateMips(scene.readingTexture.get()); @@ -605,13 +605,25 @@ void YACReaderFlow3D::prepareDrawData(const YACReader3DImageRHI &image, bool isR // Store per-instance rotation in the instance data (new slot at index 22) outInstanceData[22] = image.current.rot; - // Prepare uniform data - outUniformData.viewProjectionMatrix = viewProjectionMatrix; - outUniformData.backgroundColor = QVector3D(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF()); - outUniformData.shadingColor = QVector3D(shadingColor.redF(), shadingColor.greenF(), shadingColor.blueF()); + // Prepare uniform data (copy float data into POD arrays) + const float *vp = viewProjectionMatrix.constData(); + for (int m = 0; m < 16; ++m) + outUniformData.viewProjectionMatrix[m] = vp[m]; + + outUniformData.backgroundColor[0] = backgroundColor.redF(); + outUniformData.backgroundColor[1] = backgroundColor.greenF(); + outUniformData.backgroundColor[2] = backgroundColor.blueF(); + outUniformData._pad0 = 0.0f; + + outUniformData.shadingColor[0] = shadingColor.redF(); + outUniformData.shadingColor[1] = shadingColor.greenF(); + outUniformData.shadingColor[2] = shadingColor.blueF(); + outUniformData._pad1 = 0.0f; + outUniformData.reflectionUp = reflectionUp; outUniformData.reflectionDown = reflectionBottom; - outUniformData.isReflection = isReflection ? 1 : 0; + outUniformData.isReflection = isReflection ? 1.0f : 0.0f; + outUniformData._pad2 = 0.0f; } void YACReaderFlow3D::executeDrawWithOffset(QRhiCommandBuffer *cb, QRhiTexture *texture, diff --git a/common/rhi/yacreader_flow_rhi.h b/common/rhi/yacreader_flow_rhi.h index 7c02b8f8..44e5e60d 100644 --- a/common/rhi/yacreader_flow_rhi.h +++ b/common/rhi/yacreader_flow_rhi.h @@ -112,14 +112,14 @@ protected: // Uniform buffer data structure (must match shader layout) struct UniformData { - QMatrix4x4 viewProjectionMatrix; - QVector3D backgroundColor; - float _pad0; - QVector3D shadingColor; - float _pad1; + float viewProjectionMatrix[16]; // column-major 4x4 + float backgroundColor[3]; + float _pad0; // pad to vec4 + float shadingColor[3]; + float _pad1; // pad to vec4 float reflectionUp; float reflectionDown; - int isReflection; + float isReflection; float _pad2; }; diff --git a/common/rhi/yacreader_page_flow_rhi.cpp b/common/rhi/yacreader_page_flow_rhi.cpp index f2e9b398..d3220071 100644 --- a/common/rhi/yacreader_page_flow_rhi.cpp +++ b/common/rhi/yacreader_page_flow_rhi.cpp @@ -40,7 +40,7 @@ void YACReaderPageFlow3D::updateImageData() // Create QRhiTexture from the loaded image and queue the pixel upload if (m_rhi) { - QRhiTexture *texture = m_rhi->newTexture(QRhiTexture::BGRA8, img.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); + QRhiTexture *texture = m_rhi->newTexture(QRhiTexture::RGBA8, img.size(), 1, QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips); if (texture->create()) { // Queue the image upload so it happens together with other resource updates @@ -136,7 +136,7 @@ QImage ImageLoaderByteArray3D::loadImage(const QByteArray &raw) break; } - return image; + return image.convertToFormat(QImage::Format_RGBA8888); } ImageLoaderByteArray3D::ImageLoaderByteArray3D(YACReaderFlow3D *flow)