Thanks for the 3.10.6 fix for swizzled normal maps ( https://feedback.vrchat.com/sdk-bug-reports/p/dpid-mip-mapping-breaks-normal-maps-if-swizzled ). On PC that case looks good in my tests now. However, normal maps without swizzle or Flip Green still seem to get broken or degraded mips, because DPID regenerates them from the raw PNG and discards the encoding that Unity's importer has already applied. A note on how this was put together: I read the SDK source in the 3.10.5 and 3.10.6 packages and ran a small test project, with help from AI assistants (Claude Opus, Claude Fable and GPT Astra) over several rounds of cross-review. I have tried to check every claim against code or measurements, but some details may still be off. Please treat this as a starting point rather than a definitive analysis. What I am seeing PNG alpha leaks into X (PC, new in 3.10.6). For PNG normal maps whose alpha channel is not fully opaque, the decoded X at every mip (mip 0 included) comes out as (X + 1) × alpha − 1 instead of X (alpha in [0, 1]), so normals lean toward −X. With alpha 128, X drops by about 0.5 at X = 0 and by about 1.0 at X = +1. With alpha 0 the result is X = −1 everywhere. This affects formats that keep alpha (RGBA32, DXT5 and BC7 in my tests); BC5 did not show it. 3.10.5 overwrote alpha with 1, which hid this. In my project, 54 of 1,424 DPID-target PNG normal maps (mostly from purchased outfits) have non-opaque alpha, and 29 of those contain fully transparent pixels. DXT5 quality loss (PC, 3.10.5 and 3.10.6). With the default Normal Quality compression, the mips (mip 0 included) show noticeably higher compression error than without DPID, at least on the detailed test texture below. Z forced to 1 (Android, 3.10.5 and 3.10.6). Every mip that DPID writes stores Z = 1, so normals are flattened on Quest. For unswizzled textures this includes mip 0. For swizzled / Flip Green textures mip 0 is kept but mips 1 and smaller are still affected, so the 3.10.6 fix does not fully cover Android. Environment VRChat SDK 3.10.6 (3.10.5 compared), Unity 2022.3.22f1, D3D11 editor DPID mipmaps on, conservative mode on Texture Type: Normal map, Mipmap Filtering: Kaiser, PNG source PC: Windows target, DXT5nm normal map encoding (the default). Android: XYZ encoding (the default) What I think is happening Notation: below, x, y, z and alpha are the stored [0, 1] channel values; X, Y, Z are the decoded [-1, 1] components. When OnPostprocessTexture runs, Unity has already re-encoded the normal map. Dumping the uncompressed import result shows: PC (DXT5nm target): each pixel is (1, y, y, x). Unity itself discards the PNG alpha at this point. Android (XYZ target): each pixel is (x, y, z, 1). The layout depends on the target format: judging from the BC5 measurements, a BC5 target appears to keep x in R. For textures with no swizzle, Flip Green or similar settings, PerceptualPostProcessor takes the fromFile path: it reloads the raw PNG as (x, y, z, alpha) via ImageConversion.LoadImage and regenerates every mip including mip 0 ( inPlace: false , so startMip is 0). The output keeps the raw layout instead of Unity's encoding. (LoadImage only handles PNG and JPG, so TGA / PSD sources fall back to the in-place path.) PC shaders decode X from R × A ( UnpackNormalmapRGorAG ; lilToon does the same). Since 3.10.6 keeps A, the PNG's alpha multiplies the stored x. 3.10.5 forced A to 1 ( pixel.ba = 1.0f ), which hid the leak on this path while destroying X on the in-place path. DXT5nm keeps X in the BC3 alpha block and only Y in the color block. In the raw layout X and Y share the color block, which loses precision. On Android, pixel.b = 1.0f in the compute shader overwrites Z on both paths, and shaders compiled with UNITY_NO_DXT5nm read xyz directly. Swizzled textures already take the in-place path, which is why 3.10.6 fixed that case on PC. Steps to reproduce (issue 1) Enable DPID mipmaps in the VRChat SDK settings. Import an RGBA PNG normal map whose alpha is below 255 (for example 128 everywhere). Texture Type: Normal map, Mipmap Filtering: Kaiser, Compression: None, Read/Write Enabled, no swizzle or Flip Green, Max Size not smaller than the image, Windows target. Read the mips with GetPixels(mip) and decode X as 2 × R × A − 1, or view it on a material: X is shifted toward −1. Set Mipmap Filtering to Box (DPID is skipped) or turn DPID off: X is correct again. For issue 2, compare a detailed opaque PNG normal map (for example the synthetic one below) at default compression with DPID on and off. For issue 3, switch to Android and check the blue channel of the mips. Measurements Test texture: 64 × 64 synthetic normal map with X = 0.7 sin(2πi/16), Y = 0.5 sin(2πj/32 + 1), Z = sqrt(1 − X² − Y²), encoded to PNG. The "alpha 128" variant has alpha 128 everywhere. Values are the mean absolute error of the decoded component against a box-averaged source (not renormalized), in [-1, 1] units (lower is better). "Mips 1-3" is the worst of mips 1 to 3. "No DPID" is Unity's own Kaiser mips, which are sharpened and so also differ from a box average. The 3.10.5 / 3.10.6 / Fix columns were produced with copies of the respective SDK code in a small test project, so please treat them as indicative rather than exact. Case 3.10.5 3.10.6 Fix No DPID PC, Flip Green, DXT5, mips 1-3, X 1.000 0.060 0.055 0.097 PC, default, DXT5, mip 0, Y 0.064 0.064 0.007 0.007 PC, default, DXT5, mips 1-3, Y 0.250 0.250 0.055 0.055 PC, alpha 128, uncompressed, mip 0, X 0.002 0.498 0.002 0.002 PC, alpha 128, DXT5, mip 0, X 0.033 0.497 0.009 0.009 PC, alpha 128, BC5, mip 0, X 0.009 0.009 0.009 0.009 Android, default, uncompressed, mip 0, Z 0.216 0.216 0.002 0.002 Android, default, uncompressed, mips 1-3, Z 0.216 0.216 0.020 0.012 Android, Flip Green, uncompressed, mips 1-3, Z 0.216 0.216 0.020 0.012 Android, default, ASTC 6x6, mip 0, Z 0.216 0.216 0.036 0.036 Android, default, ASTC 6x6, mips 1-3, Z 0.216 0.216 0.072 0.074 BC5 did not show the alpha shift in this test (uniform alpha); I have not tested spatially varying alpha. Proposed fix (for discussion) Mip normal maps in place, so DPID works on the texture Unity has already encoded, and stop overwriting channels. This is what I am running locally and it seems to behave, but I have only tested the cases listed here. Editor/VRCSDK/PerceptualPostProcessor.cs , after the existing fromFile checks: ```csharp if (normalMap) fromFile = false; `Editor/VRCSDK/Dependencies/VRChat/Resources/PerceptualMipmapping/PerceptualPostProcessor.compute`, in `Downsampling()`, remove the overwrite: ```hlsl float4 pixel = _Input[int2(x, y)]; // removed: if (normalMap) pixel.b = 1.0f; With both changes, the cases I tested came out comparable to Unity's own Kaiser mips: uncompressed, DXT5, BC7 and BC5 on PC, and uncompressed and ASTC on Android, each with default, Flip Green, OneMinusG swizzle and alpha-PNG sources. "Comparable" rather than "identical": in a few cases the fixed DPID mips were slightly further from the box average than Unity's (for example Android uncompressed mips 1-3 Z, 0.020 vs 0.012), which I read as DPID's sharpening rather than an encoding error. With ETC (not my project's default) the fix's Y / Z error at mips 1-3 was somewhat higher than Unity's while X was lower, so I would not call that case a clear win either way. Things to be aware of: For normal maps larger than Max Size, mip 0 becomes Unity's downscale instead of DPID's, and the smaller mips are generated from that. In the DXT5nm layout B duplicates G, so Y counts twice in the DPID distance. I could not measure a difference on the test texture, but I have not looked further. After applying the code change, existing normal maps keep their current mips until they are reimported. (The SDK's DPID toggle already reimports Kaiser textures, but a code change alone does not.)