0
\$\begingroup\$

I'm currently using a GPU shader to generate a texture. I want to be able to capture the output of this out I'm using Graphics.Blit to output the shaders result to a RenderTexture(GPU Memory) and then using Texture2D.ReadPixels(System Memory) to grab the data from the RenderTexture.

So far I've got it almost perfect, unfortunately, I'm noticing a significant difference in quality between a plane that uses the shader output directly and a plane that uses the Texture2D returned from my method and applied to the shared material.

The process of my static method is as follows:

  1. Check if the static material variable is set. If not, create a new instance using the shader and assign it to, the static material variable, otherwise reuse the existing one.

  2. Create a RenderTexture that uses the same width and height scale as the plane (128x128) using RenderTexture.GetTemporary() which uses ARGB32, Linear colorspace and 0 depth buffer.

  3. Set the material keywords and properties based on method input. Call Graphics.Blit() with null, RenderTexture(from 2) and material (from 1). Create a new Texture2D with the same dimensions as the render texture, ARGB32 and using linear colour space.

  4. Called GetPixels() on the above Texture2D with parameters, of a new Rect of (0,0) and render texture dimensions and 0 for offsets.

  5. Call Apply() on the Texture2D and return it. I then use a simple editor script for the class that adds a button to call the above static function and then apply the output to the material of the object the script is on.

I don't know what's causing the quality loss, I've added a screenshot of what I'm currently getting below. The left plane is steps detailed above and the right plane is with the shader applied directly to a plane. Both use the same shader properties and keywords.

Image of two quads showing a blotchy black & white image. The one on the left is noticeably lower-resolution and shows bilinear filtering artifacts from upscaling

I don't normally post questions but I've tried everything I can think of to get the quality of texture to match the shader output. I'm wondering if I'm misunderstanding something about how the shader works or if it's a formatting issue. Any ideas are appreciated, thanks.

\$\endgroup\$
  • \$\begingroup\$ can you put some code in your question? I'm sure that problem is In your RenderTexture code please try by using directly RenderTexture and tell result I think your RenderTexture Is low quality.try this Rt = new RenderTexture(1024,1024,0);. \$\endgroup\$ – Seyed Morteza Kamali Feb 21 '18 at 4:56
  • \$\begingroup\$ Thanks for the reply but the issue seems to be that the shader is a 1:1 with the plane so it produces a resolution that is perfect for the specific object. I simply generate the noise at a fixed power of 2 sizes now and then resize the mesh according. The purpose of this is only for previewing the result of the shader I'm using, as it is a noise generating shader I'm only wanting to use the values it produces but unity doesn't allow any other way to access shader output other than copying it to a texture. Accepted answer solved my issue. \$\endgroup\$ – Orsidus Feb 21 '18 at 19:10
1
\$\begingroup\$

When you draw a piece of geometry with a shader directly, the GPU evaluates that shader once for every screen pixel the geometry covers. (Or more, if you're using SSAA) Every single one of those evaluations can return a distinct value, giving you sharp, crisp edges and smoothly curving gradations limited only by your screen resolution.

When you render the output of that shader into a texture using something like Graphics.Blit, you get one evaluation of the shader per texel in the texture. This could be a greater or lesser number, depending on your texture resolution and the size you'll ultimately be drawing the geometry to the screen.

In this particular example, you mentioned that your texture is 128x128 texels - fairly small by modern standards.

Measuring the screengrab you posted, it looks like the quad you're rendering occupies a region of the screen between 450-550 pixels square.

So the direct shader rendering on the right, getting evaluated once for every screen pixel, has close to 4 times the resolution as the render-to-texture version on the left.

In contrast, the rendered texture has only one texel to sample for every 16 (4x4) screen pixels being drawn, and the GPU has to magnify & interpolate the texels with bilinear filtering to fill in the gaps, leading to the noticeable staircase shapes and fuzzy edges.

So, things you can do:

  1. Stick to rendering materials directly on the surfaces you want to display them on, when you can.

    This is the case that all our texture filtering & antialiasing hardware & algorithms are designed to handle best, and it gives you full detail at whatever resolution the object happens to be drawn at.

  2. Where you need to write a result to an off-screen buffer, try to size that buffer appropriately to the size/density of texture detail you ultimately want to display on screen.

    This might entail changing the buffer size as objects & the camera move around your scene - to avoid excess rendering objects that are tiny on-screen, and blurry results for objects that appear large. Fortunately, you don't need to GetPixels the result onto a Texture2D to use them - you can slap a RenderTexture straight onto the object you want to draw and save yourself some copying / texture management when juggling multiple sizes.

\$\endgroup\$
  • \$\begingroup\$ Thanks, this is definitely my issue, I wasn't understanding how the surface of the shader worked properly as this isn't a shader I made myself but just a public shader I modified for my use to experiment with. Thanks for your answer. \$\endgroup\$ – Orsidus Feb 21 '18 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.