├── .gitignore ├── README.md ├── README.md.meta ├── Resources.meta ├── Resources ├── JPEGMP4Compression.compute ├── JPEGMP4Compression.compute.meta ├── MotionVectorSource.shader └── MotionVectorSource.shader.meta ├── Runtime.meta ├── Runtime ├── JPEG_MP4_Compression.cs ├── JPEG_MP4_Compression.cs.meta ├── JPEG_MP4_Compression_Renderer.cs ├── JPEG_MP4_Compression_Renderer.cs.meta ├── com.jamathan.jpegmp4compression.asmdef └── com.jamathan.jpegmp4compression.asmdef.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanMalitschek/JPEG-MP4-Compression-PostProcessing-Effect-for-Unity3D/226f3015d4eacf8b62840dde226ed6a482345565/.gitignore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JPEG/MP4 Compression PostProcessing Effect for Unity3D 2 | This package aims to accurately recreate the effect of JPEG/MP4 compression as a PostProcessing Effect. 3 | 4 | ## Important! 5 | This package was designed for the Unity Package Manager! It won't work by simply dragging it into your project. 6 | Instead open the Package Manager Window, click the little + button in the top left corner and choose Git URL. 7 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6eccb9d6784eadc4b8033bbd9db2d973 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 22f1fe13f86776243ae3516b622368c0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources/JPEGMP4Compression.compute: -------------------------------------------------------------------------------- 1 | //Two different kernel configurations for both performance modes 2 | #pragma kernel CSMain ACCURATE 3 | #pragma kernel CSMain FAST 4 | 5 | //The Input/Output Textures 6 | Texture2D Last; 7 | Texture2D Input; 8 | Texture2D Motion; 9 | RWTexture2D Result; 10 | 11 | //User Settings 12 | //Spacial Compression Settings 13 | bool UseSpacial; 14 | float CompressionThreshold; 15 | 16 | //Temporal Compression Settings 17 | bool UseTemporal; 18 | bool UseIFrames; 19 | float Bitrate; 20 | float BitrateArtifacts; 21 | //Deternimes if the current frame should be an I-Frame 22 | bool IsIFrame; 23 | 24 | //Used to quantize the chrominance in Fast mode 25 | float Quantize(float input, int resolution){ 26 | input *= resolution; 27 | input = floor(input.x); 28 | input /= resolution; 29 | return input; 30 | } 31 | 32 | //Yuv to RGB color space 33 | float3 YuvToRGB(float3 yuv){ 34 | float3 color; 35 | color.b = yuv.x + (1.0/0.493) * yuv.y; 36 | color.r = yuv.x + (1.0/0.877) * yuv.z; 37 | color.g = (1.0/0.587) * yuv.x - (0.299/0.587) * color.r - (0.114/0.587) * color.b; 38 | return color; 39 | } 40 | 41 | //RGB to Yuv color space 42 | float3 RGBToYuv(float3 color){ 43 | float Y = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b; 44 | float u = 0.493 * (color.b - Y); 45 | float v = 0.877 * (color.r - Y); 46 | //In case we are using the Fast performance mode we will crunch down on the chrominance values during the conversion 47 | #ifdef FAST 48 | u = Quantize(u, lerp(256, 32, CompressionThreshold * 0.5)); 49 | v = Quantize(v, lerp(256, 32, CompressionThreshold * 0.5)); 50 | #endif 51 | return float3(Y, u, v); 52 | } 53 | 54 | //Utility function to make sure a pixel position is within the bounds of the visible screen 55 | bool IsInBounds(int2 pos, int w, int h){ 56 | return pos.x >= 0 && pos.x < w - 8 && pos.y >= 0 && pos.y < h - 8; 57 | } 58 | 59 | //Shared memory for the color information 60 | groupshared float3 pixelBlock[8][8]; 61 | //Shared memory for the spectral information 62 | groupshared float3 dct[8][8]; 63 | 64 | //Shared memory to determine if a pixelBlock should be updated 65 | //Only used in the MP4 compression 66 | groupshared bool update; 67 | //Shared memory that holds a single B-Frame offset for the whole pixelBlock 68 | groupshared int2 offset; 69 | 70 | [numthreads(8,8,1)] 71 | //8x8 threads are ideal for this algorithm since the Discrete Cosine Transform used in JPEG compression operates 72 | //on an 8x8 block of pixels 73 | void CSMain (uint3 id : SV_DispatchThreadID, uint3 groupThreadID : SV_GroupThreadID, uint3 groupID : SV_GroupID) 74 | { 75 | //Loop indices are defined outside of any loops because HLSL likes to complain about it otherwise 76 | int ix, iy; 77 | 78 | //Fetch Input Pixels and Convert to Yuv 79 | pixelBlock[groupThreadID.x][groupThreadID.y] = RGBToYuv(Input[groupThreadID.xy + groupID.xy * 8].rgb); 80 | GroupMemoryBarrierWithGroupSync(); 81 | 82 | //The entire temporal compression process takes place here 83 | if(UseTemporal){ 84 | //Read the normalized motion vectors we got from the MotionVectorSource shader 85 | float2 motion = Motion[groupThreadID.xy + groupID.xy * 8].rg; 86 | //Check that the groupThreadID is 0 87 | //This next part determines if this pixelBlock should be updated and how, 88 | //thus it only needs to be executed once per thread group 89 | if(groupThreadID.x == 0 && groupThreadID.y == 0){ 90 | //Motion Vectors indicate the screenspace/uv position change of a pixel 91 | //To convert these normalized screen positions to actual pixel coordinates we simply need to multiply them 92 | //by the screens dimensions, which can be obtained as follows: 93 | uint w, h; 94 | Result.GetDimensions(w, h); 95 | //Now apply the dimensions to the read motion vector 96 | float2 pixelMotion = motion * float2(w, h); 97 | //write the result into the groupshared offset variable 98 | offset = int2(pixelMotion.x, pixelMotion.y); 99 | //There are multiple conditions that control wether a pixelBlock should be updated or not 100 | //One of them is the difference in luminance from the last frame 101 | float YChange = abs(pixelBlock[0][0].r - RGBToYuv(Last[groupID.xy * 8].rgb).r); 102 | //Now we write to the groupshared update variable to mark this entire pixelBlocks state 103 | //Conditions for updating a pixelBlock 104 | //Condition 1 - If the current pixelLocation + the motion vector ends up outside of the screen bounds, 105 | //we need to fetch a fresh block of pixels, else we will end up with black 106 | update = !IsInBounds(groupThreadID.xy + groupID.xy * 8 - offset, w, h) 107 | //Here we simply check if the luminance difference exceeds a certain threshold, 108 | //which is dependant on the Birate 109 | || YChange > (1.0 - Bitrate) 110 | //Of course we also need to update if the current frame is an I-Frame 111 | || IsIFrame; 112 | } 113 | GroupMemoryBarrierWithGroupSync(); 114 | //In case we don't have to update/refresh this pixelBlock we simply use the motion vector to obtain moved 115 | //color values from the last frame 116 | if(!update){ 117 | //Here we make use of the groupshared offset value 118 | Result[groupThreadID.xy + groupID.xy * 8] = Last[groupThreadID.xy + groupID.xy * 8 - offset]; 119 | return; 120 | } 121 | } 122 | 123 | //This part contains the Spacial Compression algorithm 124 | if(UseSpacial){ 125 | //Perform the DCT on each pixel in this thread group 126 | dct[groupThreadID.x][groupThreadID.y] = 0.0; 127 | for(ix = 0; ix < 8; ix++){ 128 | for(iy = 0; iy < 8; iy++){ 129 | float factor = cos((3.141592654 * (2 * ix + 1) * groupThreadID.x) / 16.0) 130 | * cos((3.141592654 * (2 * iy + 1) * groupThreadID.y) / 16.0); 131 | dct[groupThreadID.x][groupThreadID.y].r += pixelBlock[ix][iy].r * factor; 132 | #ifdef ACCURATE 133 | dct[groupThreadID.x][groupThreadID.y].g += pixelBlock[ix][iy].g * factor; 134 | dct[groupThreadID.x][groupThreadID.y].b += pixelBlock[ix][iy].b * factor; 135 | #endif 136 | } 137 | } 138 | GroupMemoryBarrierWithGroupSync(); 139 | 140 | //Quantize the DCT coefficients 141 | //In reality this uses 8x8 Quantization tables for luminance and chrominance, 142 | //however simply eliminating all coefficients below a set threshold works just as well 143 | if(abs(dct[groupThreadID.x][groupThreadID.y].r) < CompressionThreshold) 144 | dct[groupThreadID.x][groupThreadID.y].r = 0.0; 145 | #ifdef ACCURATE 146 | if(abs(dct[groupThreadID.x][groupThreadID.y].g) < CompressionThreshold) 147 | dct[groupThreadID.x][groupThreadID.y].g = 0.0; 148 | if(abs(dct[groupThreadID.x][groupThreadID.y].b) < CompressionThreshold) 149 | dct[groupThreadID.x][groupThreadID.y].b = 0.0; 150 | #endif 151 | GroupMemoryBarrierWithGroupSync(); 152 | 153 | //Perform the inverse DCT 154 | #ifdef ACCURATE 155 | pixelBlock[groupThreadID.x][groupThreadID.y] = 0.0; 156 | #elif FAST 157 | pixelBlock[groupThreadID.x][groupThreadID.y].r = 0.0; 158 | #endif 159 | for(ix = 0; ix < 8; ix++){ 160 | for(iy = 0; iy < 8; iy++){ 161 | float3 dctTemp = dct[ix][iy]; 162 | dctTemp *= (ix == 0 ? 0.353553390593 : 0.5); 163 | dctTemp *= (iy == 0 ? 0.353553390593 : 0.5); 164 | float factor = cos((3.141592654 * (2 * groupThreadID.x + 1) * ix) / 16.0) 165 | * cos((3.141592654 * (2 * groupThreadID.y + 1) * iy) / 16.0); 166 | pixelBlock[groupThreadID.x][groupThreadID.y].r += dctTemp.r * factor; 167 | #ifdef ACCURATE 168 | pixelBlock[groupThreadID.x][groupThreadID.y].g += dctTemp.g * factor; 169 | pixelBlock[groupThreadID.x][groupThreadID.y].b += dctTemp.b * factor; 170 | #endif 171 | } 172 | } 173 | GroupMemoryBarrierWithGroupSync(); 174 | 175 | //Convert to RGB and output 176 | //When using Temporal Compression we need to look out for Bitrate Artifacts 177 | if(UseTemporal){ 178 | //We simply lerp between the processed color and the last frame using the BitrateArtifacts parameter as the percentage 179 | #ifdef ACCURATE 180 | Result[groupThreadID.xy + groupID.xy * 8] = lerp(float4(YuvToRGB(pixelBlock[groupThreadID.x][groupThreadID.y] * 0.125), 1.0), 181 | Last[groupThreadID.xy + groupID.xy * 8 - offset], BitrateArtifacts); 182 | #elif FAST 183 | Result[groupThreadID.xy + groupID.xy * 8] = lerp(float4(YuvToRGB(pixelBlock[groupThreadID.x][groupThreadID.y] * float3(0.125, 1.0, 1.0)), 1.0), 184 | Last[groupThreadID.xy + groupID.xy * 8 - offset], BitrateArtifacts); 185 | #endif 186 | } 187 | //If not, we simply read the processed color from the pixelBlock 188 | else{ 189 | #ifdef ACCURATE 190 | Result[groupThreadID.xy + groupID.xy * 8] = float4(YuvToRGB(pixelBlock[groupThreadID.x][groupThreadID.y] * 0.125), 1.0); 191 | #elif FAST 192 | Result[groupThreadID.xy + groupID.xy * 8] = float4(YuvToRGB(pixelBlock[groupThreadID.x][groupThreadID.y] * float3(0.125, 1.0, 1.0)), 1.0); 193 | #endif 194 | } 195 | } 196 | else 197 | //If we don't want to use Spacial compression we can just convert the input colors back to RGB and output them 198 | Result[groupThreadID.xy + groupID.xy * 8] = float4(YuvToRGB(pixelBlock[groupThreadID.x][groupThreadID.y]), 1.0); 199 | } -------------------------------------------------------------------------------- /Resources/JPEGMP4Compression.compute.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 63806c3726d7c314bbdbcc2efb3fe71e 3 | ComputeShaderImporter: 4 | externalObjects: {} 5 | currentAPIMask: 4 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources/MotionVectorSource.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/MotionVectorSource" 2 | { 3 | //We don't need any properties, not even an input texture as this shader's only purpose is to sample 4 | //the motion vector uniform texture 5 | Properties{} 6 | SubShader 7 | { 8 | // No culling or depth 9 | Cull Off ZWrite Off ZTest Always 10 | 11 | Pass 12 | { 13 | //Nothing has changed here at all 14 | CGPROGRAM 15 | #pragma vertex vert 16 | #pragma fragment frag 17 | 18 | #include "UnityCG.cginc" 19 | 20 | struct appdata 21 | { 22 | float4 vertex : POSITION; 23 | float2 uv : TEXCOORD0; 24 | }; 25 | 26 | struct v2f 27 | { 28 | float2 uv : TEXCOORD0; 29 | float4 vertex : SV_POSITION; 30 | }; 31 | 32 | v2f vert (appdata v) 33 | { 34 | v2f o; 35 | o.vertex = UnityObjectToClipPos(v.vertex); 36 | o.uv = v.uv; 37 | return o; 38 | } 39 | 40 | //Make sure to define this before using it 41 | //This is a uniform provided by the engine which contains the main cameras motion vector texture 42 | sampler2D _CameraMotionVectorsTexture; 43 | 44 | fixed4 frag (v2f i) : SV_Target 45 | { 46 | //Sample the motion vectors, no fancy maths required 47 | return tex2D(_CameraMotionVectorsTexture, i.uv); 48 | } 49 | ENDCG 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Resources/MotionVectorSource.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5dac3bdfceac18b488060580fa0d5c83 3 | ShaderImporter: 4 | externalObjects: {} 5 | defaultTextures: [] 6 | nonModifiableTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ca6cd666be744f743aa1b1524a7429d2 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/JPEG_MP4_Compression.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Rendering.PostProcessing; 5 | 6 | //A custom enum parameter determining the performance mode 7 | public enum JPEGPerformance{ 8 | Accurate = 0, 9 | Fast = 1 10 | } 11 | [System.Serializable] 12 | public sealed class JPEGPerformanceParameter : ParameterOverride{} 13 | 14 | [System.Serializable] 15 | [PostProcess(typeof(JPEG_MP4_Compression_Renderer), PostProcessEvent.AfterStack, "Custom/JPEG\\MP4 Compression")] 16 | public sealed class JPEG_MP4_Compression : PostProcessEffectSettings 17 | { 18 | [Header("Spatial Compression")] 19 | //useSpacialCompression controls if the classic JPEG compression should be applied 20 | [Tooltip("Use Spatial Compression")] 21 | public BoolParameter useSpatialCompression = new BoolParameter { value = true }; 22 | //screenDownsampling determines by which number + 1 the screen resolution should be divided 23 | //Can help increase performance or simply create an even lower quality look 24 | [Tooltip("Updates on Play")] 25 | public IntParameter screenDownsampling = new IntParameter { value = 0 }; 26 | //usePointFiltering enables or disables point filtering for all RenderTextures used behind the scenes 27 | [Tooltip("Updates on Play")] 28 | public BoolParameter usePointFiltering = new BoolParameter { value = false }; 29 | //compressionThreshold determines how intensely the image should be compressed 30 | //0.0 is no compression 31 | //2.0 is extreme compression 32 | [Range(0.0f, 2.0f), Tooltip("Compression Threshold")] 33 | public FloatParameter compressionThreshold = new FloatParameter { value = 0.0f }; 34 | //performanceMode can help increase the performance 35 | //Accurate will compress all 3 color channels using DCT 36 | //Fast will only compress the luminance channel with DCT and the chrominance channels with simple quantization 37 | [Tooltip("Performance Mode")] 38 | public JPEGPerformanceParameter performanceMode = new JPEGPerformanceParameter { value = JPEGPerformance.Accurate }; 39 | //These Parameters are still WIP and will not look accurate 40 | //useBitrate enables or disables the MP4 interframe compression 41 | 42 | //Temporal Compression only works when the game is actually running since motion vectors are not available otherwise 43 | [Header("Temporal Compression (Playmode Only)")] 44 | //useTemporalCompression controls wether to apply Interframe compression effects 45 | [Tooltip("Use Temporal Compression")] 46 | public BoolParameter useTemporalCompression = new BoolParameter { value = false }; 47 | //I-Frames force a complete screen update every few frames(gap depending on the number of B-Frames) 48 | //For certain effects like Datamoshing you might want to disable them completely though 49 | [Tooltip("Use I-Frames")] 50 | public BoolParameter useIFrames = new BoolParameter { value = true }; 51 | //B-Frames try to predict the look of the next frame by moving tiny parts of the previous frame around 52 | //The more B-Frames you use the more noticable the effect 53 | [Tooltip("Number of predicted frames")] 54 | public IntParameter numBFrames = new IntParameter { value = 8}; 55 | //bitrate controls how many pixelBlocks lag behind on each frame 56 | //1.0 is none 57 | //0.0 is a lot 58 | //For reasonable effects you should keep it around 0.9 59 | //I won't stop you from going crazy with it though 60 | [Range(0.0f, 1.0f), Tooltip("Bitrate")] 61 | public FloatParameter bitrate = new FloatParameter { value = 1.0f }; 62 | //When a pixelBlock is updated it might still leave behind some artifacts of it's previous contents 63 | //Use bitrateArtifacts to control how much should bleed through 64 | [Range(0.0f, 0.95f)] 65 | public FloatParameter bitrateArtifacts = new FloatParameter { value = 0.0f }; 66 | } -------------------------------------------------------------------------------- /Runtime/JPEG_MP4_Compression.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5cc8258d068e1942a8e04d2fa6528f4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/JPEG_MP4_Compression_Renderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Rendering.PostProcessing; 5 | using UnityEngine.Rendering; 6 | using UnityEngine.Experimental.Rendering; 7 | 8 | public sealed class JPEG_MP4_Compression_Renderer : PostProcessEffectRenderer 9 | { 10 | //The intermediate render targets 11 | //The Indentifier objects are necessary in order to use the PPSV2 Blit methods 12 | //lastFrame stores the lastFrame that was rendered 13 | private RenderTargetIdentifier lastFrameIdentifier; 14 | private RenderTexture lastFrame; 15 | //sourceFrame stores the fresh image without any effects on it 16 | private RenderTargetIdentifier sourceFrameIdentifier; 17 | private RenderTexture sourceFrame; 18 | //processedFrame stores the processed/compressed image 19 | private RenderTargetIdentifier processedFrameIdentifier; 20 | private RenderTexture processedFrame; 21 | //processedFrame stores the processed/compressed image 22 | private RenderTargetIdentifier motionFrameIdentifier; 23 | private RenderTexture motionFrame; 24 | 25 | //The compute shader doing all the work 26 | private ComputeShader dctShader; 27 | //The Material used to obtain Unity motion vectors 28 | private Material motionMaterial; 29 | 30 | //This keeps track of how many B-Frames remain until the next I-Frame 31 | private int frameIndex; 32 | 33 | private void InitRenderTextures(){ 34 | //Calculate the downsampling rate in advance since we will reuse it quite a lot 35 | int downSamplingRate = Mathf.Max(1, (settings.screenDownsampling + 1)); 36 | 37 | //In case we don't want to render directly to the screen, but to a RenderTexture instead, 38 | //we need to check if the main cameras targetTexture is not null 39 | Vector2Int dimensions; 40 | //targetTexture is null, thus we're rendering to the screen and need to use the screen dimensions 41 | if(Camera.main.targetTexture == null) 42 | dimensions = new Vector2Int(Screen.width / downSamplingRate, Screen.height / downSamplingRate); 43 | //targetTexture is not null, thus we're rendering to a RenderTexture and need to use it's dimensions 44 | else 45 | dimensions = new Vector2Int(Camera.main.targetTexture.width / downSamplingRate, 46 | Camera.main.targetTexture.height / downSamplingRate); 47 | 48 | //Initialize the lastFrame render target 49 | lastFrame = new RenderTexture(dimensions.x, dimensions.y, 16); 50 | lastFrame.filterMode = settings.usePointFiltering ? FilterMode.Point : FilterMode.Bilinear; 51 | lastFrame.Create(); 52 | lastFrameIdentifier = new RenderTargetIdentifier(lastFrame); 53 | 54 | //Initialize the sourceFrame render target 55 | sourceFrame = new RenderTexture(dimensions.x, dimensions.y, 16); 56 | sourceFrame.filterMode = settings.usePointFiltering ? FilterMode.Point : FilterMode.Bilinear; 57 | sourceFrame.Create(); 58 | sourceFrameIdentifier = new RenderTargetIdentifier(sourceFrame); 59 | 60 | //Initialize the processed render target 61 | processedFrame = new RenderTexture(dimensions.x, dimensions.y, 16); 62 | //enableRandomWrite must be enabled since this is the texture the compute shader will write to 63 | processedFrame.enableRandomWrite = true; 64 | processedFrame.filterMode = settings.usePointFiltering ? FilterMode.Point : FilterMode.Bilinear; 65 | processedFrame.Create(); 66 | processedFrameIdentifier = new RenderTargetIdentifier(processedFrame); 67 | 68 | //Initialize the motion vector render target 69 | motionFrame = new RenderTexture(dimensions.x, dimensions.y, 16, 70 | GraphicsFormat.R16G16B16A16_SNorm); //This GraphicsFormat is very important! 71 | //In order to make proper use of the motion vectors we need a render target 72 | //with signed values as the motion vectors are in the range of -1.0 to 1.0 73 | motionFrame.enableRandomWrite = true; 74 | motionFrame.filterMode = settings.usePointFiltering ? FilterMode.Point : FilterMode.Bilinear; 75 | motionFrame.Create(); 76 | motionFrameIdentifier = new RenderTargetIdentifier(motionFrame); 77 | } 78 | 79 | public override void Init(){ 80 | //Load the ComputeShader from Resources 81 | dctShader = (ComputeShader)Resources.Load("JPEGMP4Compression"); 82 | motionMaterial = new Material(Shader.Find("Hidden/MotionVectorSource")); 83 | 84 | //Initialize the FrameIndex 85 | //This will be 0 at first because the first frame should be an I-Frame 86 | frameIndex = 0; 87 | 88 | //Initialize all required render targets 89 | InitRenderTextures(); 90 | 91 | //Make sure the camera renders motion Vectors 92 | Camera.main.depthTextureMode = DepthTextureMode.MotionVectors; 93 | } 94 | 95 | public override void Render(PostProcessRenderContext context){ 96 | //In case any of the render targets gets destroyed, recreate them 97 | //Usually only happens when quitting the game in the editor 98 | if(lastFrame == null || sourceFrame == null || processedFrame == null || motionFrame == null) 99 | InitRenderTextures(); 100 | 101 | //Blit context.source to our sourceFrame RenderTexture so that we can operate on it 102 | context.command.Blit(context.source, sourceFrameIdentifier); 103 | 104 | //Blit the Motion Vectors to our motionFrame render target 105 | context.command.Blit(motionFrame, motionFrameIdentifier, motionMaterial); 106 | 107 | //Create the kernel Handle and increment it by one if Fast mode is enabled 108 | //This way it will then use the second kernel configuration 109 | int mainKernelHandle = 0; 110 | if(settings.performanceMode.value == JPEGPerformance.Fast) 111 | mainKernelHandle = 1; 112 | 113 | //Pass all the RenderTextures to the shader 114 | dctShader.SetTexture(mainKernelHandle, "Last", lastFrame); 115 | dctShader.SetTexture(mainKernelHandle, "Input", sourceFrame); 116 | dctShader.SetTexture(mainKernelHandle, "Motion", motionFrame); 117 | dctShader.SetTexture(mainKernelHandle, "Result", processedFrame); 118 | //Pass the user settings to the shader 119 | dctShader.SetBool("UseSpacial", settings.useSpatialCompression); 120 | dctShader.SetFloat("CompressionThreshold", settings.compressionThreshold); 121 | dctShader.SetBool("UseTemporal", settings.useTemporalCompression.value && Application.isPlaying); 122 | dctShader.SetFloat("Bitrate", settings.bitrate); 123 | dctShader.SetFloat("BitrateArtifacts", settings.bitrateArtifacts); 124 | //If there are no B-Frames remaining the next frame will be an I-Frame 125 | //Motion Vectors do not work in the editor window, so we have to make sure that this only takes effect when 126 | //the game is running 127 | if(frameIndex == 0 || !Application.isPlaying){ 128 | dctShader.SetBool("IsIFrame", true); 129 | frameIndex = Mathf.Max(settings.numBFrames, 0); 130 | } 131 | //Otherwise just render another B-Frame 132 | else{ 133 | dctShader.SetBool("IsIFrame", false); 134 | //A B-Frame was rendered so we decrease the FrameIndex 135 | //Only if we want to actually use I-Frames though 136 | if(settings.useIFrames) frameIndex--; 137 | } 138 | //Dispatch the shader 139 | //Since each Thread group operates on an 8x8 pixel area we can divide the screen resolution by 8 to determine 140 | //how many threadgroups are necessary 141 | //Adding 7 before dividing is some integer magic that prevents potential spawning of offscreen threads 142 | dctShader.Dispatch(mainKernelHandle, (sourceFrame.width + 7) / 8, (sourceFrame.height + 7) / 8, 1); 143 | //Blit the processed image to the context's destination texture 144 | context.command.BlitFullscreenTriangle(processedFrameIdentifier, context.destination); 145 | //In case we're using the MP4 compression the destination needs to be copied to the lastFrame render target 146 | context.command.Blit(context.destination, lastFrameIdentifier); 147 | } 148 | 149 | public override void Release(){ 150 | //Release all render targets 151 | lastFrame.Release(); 152 | sourceFrame.Release(); 153 | processedFrame.Release(); 154 | motionFrame.Release(); 155 | } 156 | } -------------------------------------------------------------------------------- /Runtime/JPEG_MP4_Compression_Renderer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bd88f68eb702c1e43b6682a4d1d6d6e6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/com.jamathan.jpegmp4compression.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.jamathan.jpegmp4compression", 3 | "references": [ 4 | "GUID:d60799ab2a985554ea1a39cd38695018", 5 | "GUID:a35efad8797223d499f8c68b1f545dbc" 6 | ], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Runtime/com.jamathan.jpegmp4compression.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 34fededef538bbc4a89d32862e989836 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.jamathan.jpegmp4compression", 3 | "version": "1.2.0", 4 | "displayName": "JPEG MP4 Compression PostProcessing Effect", 5 | "description": "This package aims to accurately recreate the effect of JPEG/MP4 compression as a PostProcessing Effect.", 6 | "unity": "2019.1", 7 | "dependencies": { 8 | "com.unity.postprocessing": "2.2.2" 9 | }, 10 | "keywords": [ 11 | "postprocessing", 12 | "compression", 13 | "jpeg", 14 | "mp4" 15 | ], 16 | "author": { 17 | "name": "Jamathan", 18 | "url": "https://twitter.com/malitschek" 19 | }, 20 | "type": "module" 21 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c935ac612aafc094cabd886f24f919a2 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------