Search Unity

How to sample R16 texture in shader?

Discussion in 'Shaders' started by tcjkant, Aug 1, 2019.

  1. tcjkant

    tcjkant

    Joined:
    Jan 12, 2019
    Posts:
    15
    I'm trying to store a bitfield in a texture for use in a shader. It seems that the only integer-based non-experimental texture format is R16, which works fine for my purposes in terms of setting the data CPU-side.

    However I'm not sure exactly how to retrieve the bits in the shader. My shaders won't compile if I declare a Texture2D<short>. It will compile with Texture2D<int>, but since the input data is a 16-bit integer, not 32-bit, it seems like it's not going to be sampled correctly if I do it that way. I'm getting strange results from the shader but am not sure whether this disparity is the culprit or some unrelated bug.

    Any best practices when it comes to using the R16 texture format?
     
    DragonCoder likes this.
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The best practice is ... do nothing special. The main thing is you'll want to use Load rather than Sample.
    Code (csharp):
    1. // define texture
    2. Texture2D _MyIntTexture;
    3.  
    4. // load texture
    5. uint val = _MyIntTexture.Load(int3(texelU, texelV, 0)).r;
     
  3. tcjkant

    tcjkant

    Joined:
    Jan 12, 2019
    Posts:
    15
    In case anyone else runs into this, try as I might I was never able to get the right data coming out of the R16 texture format. I switched to the RFloat texture format and used unit32s to store the bitfields and it worked fine (so long as you use asuint() when you load the texel in the shader).
     
    FurySven and Hazkin like this.
  4. KayH

    KayH

    Joined:
    Jan 6, 2017
    Posts:
    110
    How can I use that syntax in a Unity shader? I get compilation errors for Texture2D and Load.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    That's syntax for a Direct3D 11 or Vulkan shader. If you're trying to do this for OpenGL or mobile you can't use
    Texture2D
    in the shader. Use
    sampler2D
    like you would any other texture and set the texture to use point filtering.
     
  6. KayH

    KayH

    Joined:
    Jan 6, 2017
    Posts:
    110
    Thanks for the explanation. I figured out to use point filtering with sampler2d myself but it's good to know why the syntax didn't work.
     
  7. mcfly3001

    mcfly3001

    Joined:
    May 20, 2016
    Posts:
    1
    Could anyone provide a small code snippet how the sampling of the R16 texture finally succeeded? From what I have read so far, it seems that reading the texture via "tex2D()" will always return floating point values. How can I make sure that the value is read as uint? Thanks!
     
  8. wechat_os_Qy06eaOhICF9NcZoMWMLtv5cI

    wechat_os_Qy06eaOhICF9NcZoMWMLtv5cI

    Joined:
    Feb 1, 2018
    Posts:
    33
    so,How to load R16 on CPU,and sample it on GPU??????
     
  9. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
  10. IgorAherneBusiness

    IgorAherneBusiness

    Joined:
    Apr 22, 2017
    Posts:
    9
    This answer comes in 2 parts.
    Part one does appear to work, but messes up two bits. But read it, to understand part 2.

    Part 1:


    In c# you create it as:
    Code (CSharp):
    1.  
    2. // NOTICE: ARGBUInt (32 bits per channel meaning four 8-bit masks fit inside a channel).
    3. //ARGBFloat was messing up due to denormalization:
    4. // https://stackoverflow.com/questions/60019943/hlsl-asuint-of-a-float-seems-to-return-the-wrong-value
    5. texture = new RenderTexture(2048, 2048, 0, UnityEngine.Experimental.Rendering.GraphicsFormat.R32G32B32A32_UInt, mipCount:0);
    6. texture.filterMode = FilterMode.Point;
    7. texture.useMipMap = false;
    8. texture.antiAliasing = 1;
    9. texture.enableRandomWrite = true;//so that our compute shaders work fine with it.
    10. texture.Create();
    11.  
    Notice, you most likely did fill this integer texture via another shader, to give it some starting values.

    In that case, you need to ensure the return type of that shader's
    frag()
    function is float4 not int4 nor uint4 like I assumed originally.

    But at the same time, you need to return it as uint4, ensuring you don't cast it to float anywhere.
    Again: return type is
    float4
    but return
    uint4
    .
    Otherwise if you attempt to sample it later on, you'd get zeros.

    For example, I am filling this integer texture as:
    Code (CSharp):
    1. //notice float4, uint4 return type doesn't work even if target texture is R32G32B32A32_UInt.
    2. float4 frag (v2f input) : SV_Target{
    3.     uint val = 0xFFFFFFFF;
    4.     return uint4(val,val,val,val); //return uint4 even though return is float4
    5. }
    In C# doing
    Graphics.Blit(null, texture, _myFillMaterial);


    In C#, apply integer texture into shader where it will be read:
    _mySamplingMaterial.SetTexture("_MyIntegerTex", texture);


    Now, in some other shader you can sample it as:
    Code (CSharp):
    1. sampler2D _MyIntegerTex;
    2.  
    3. float4 frag(v2f input) : SV_Target {
    4.         //notice: storing directly into uint4:
    5.        uint4 rgba128 =  tex2D(_MyIntegerTex, input.uv).xyzw;
    6. }
    As a final word of caution, avoid using
    asuint()
    because it makes your uint zero if float had some special bits flipped:

    https://stackoverflow.com/questions/60019943/hlsl-asuint-of-a-float-seems-to-return-the-wrong-value

    https://discussions.unity.com/t/how-do-int-textures-work-in-computeshaders/246832/3
     
    Last edited: Mar 8, 2024
    tun1018 likes this.
  11. IgorAherneBusiness

    IgorAherneBusiness

    Joined:
    Apr 22, 2017
    Posts:
    9
    Part 2:

    My answer above does work, but it still messes up the upper 2 bits due to
    float4 frag().
    So, here is the solution to that, using compute shaders:


    So, we need to find a way to fill the unsigned-integer-texture with
    uint4 

    Unity doesn't seem to allow returning
    uint4
    from
    frag()
    in shader. It compiles but returns zeros.

    Therefore, we can do it via compute shader. Create one and put this code:

    Code (CSharp):
    1. // ComputeShaderExample.compute
    2. #pragma kernel CSMain
    3.  
    4. // Declare the RWTexture with uint4 since we're working with unsigned integers
    5. RWTexture2D<uint4> Result;
    6.  
    7. [numthreads(8, 8, 1)]
    8. void CSMain (uint3 id : SV_DispatchThreadID) {
    9.     // Example unsigned integer values to write to the texture
    10.     uint4 values = uint4(255, 255, 255, 255);
    11.  
    12.     // Writing the unsigned integer values to the texture
    13.     Result[id.xy] = values;
    14. }
    In your C# script, to populate the unsigned-integer-texture with values do:
    Code (CSharp):
    1. [SerializeField] ComputeShader _myComputeShader;
    2.  
    3. RenderTexture texture = /*make or get your render texture here*/
    4. texture.enableRandomWrite = true;//so that our compute shaders work fine with it.
    5.  
    6. int kernelHandle = _maskFilingShader.FindKernel("CSMain");
    7. _myComputeShader.SetTexture(kernelHandle, "Result", texture, 0);
    8. _myComputeShader.Dispatch(kernelHandle, texture.width/8, texture.height/8, 1);
    Adjust the 8 and 1 depending on your hardware, to improve performance.
    If you do, change it both in
    [numthreads(8, 8, 1)]
    and also in c#
    .Dispatch()


    However, the slightly tricky part is reading your unsigned-integer-texture in your shader.

    From C#, assign your texture into material:

    Code (CSharp):
    1. _myMaterial.SetTexture("_MyUIntegerTexture", texture);
    2. _myMaterial.SetInt("_MyUIntegerTexture_Width", texture.width);
    3. _myMaterial.SetInt("_MyUIntegerTexture_Height", texture.height);
    And in shader do:
    Code (CSharp):
    1. Texture2D<uint4> _MyUIntegerTexture;
    2. int _MyUIntegerTexture_Width;
    3. int _MyUIntegerTexture_Height;
    4.  
    5. fixed4 frag(v2f input) : SV_Target{
    6.      uint3 sampleCoord = uint3(input.uv.x*_MyUIntegerTexture_Width, input.y*_MyUIntegerTexture_Height, 0);
    7.      uint4 rgba128 =  _MyUIntegerTexture.Load(sampleCoord);
    8. }
    Notice that we did Texture2D<uint4> and not just Texture2D nor sampler2D. Otherwise it would return zeros.
    Also, notice that we assigned result to uint4 rgba128 and didn't convert to float or other types, to avoid losing the values in the process.

    There are important details about
    SV_DispatchThreadID, SV_GroupID, SV_GroupThreadID

    You can read about them here and check unity example here.
    In many cases we can just use
    SV_DispatchThreadID
    . For out-of-bounds checks see here
    Or if that post ever gets removed, here is quick copy:
    SV_DispatchThread ID (uint3)
    Indices for which combined thread and thread group a compute shader is executing in. SV_DispatchThreadID is the sum of SV_GroupID * numthreads and GroupThreadID. It varies across the range specified in Dispatch and numthreads. For example, if Dispatch(2,2,2) is called on a compute shader with numthreads(3,3,3) SV_DispatchThreadID will have a range of 0..5 for each dimension.

    SV_GroupID (uint3)
    Indices for which thread group a compute shader is executing in. The indices are to the whole group and not an individual thread. Possible values vary across the range passed as parameters to Dispatch. For example calling Dispatch(2,1,1) results in possible values of 0,0,0 and 1,0,0.

    Defines the group offset within a Dispatch call, per dimension of the dispatch call.

    SV_GroupThreadID (uint3)
    Indices for which an individual thread within a thread group a compute shader is executing in. SV_GroupThreadID varies across the range specified for the compute shader in the numthreads attribute. For example, if numthreads(3,2,1) was specified possible values for the SV_GroupThreadID input value have this range of values (0–2,0–1,0).

    SV_GroupIndex (uint)
    The “flattened” index of a compute shader thread within a thread group, which turns the multi-dimensional SV_GroupThreadID into a 1D value. SV_GroupIndex varies from 0 to (numthreadsX * numthreadsY * numThreadsZ) - 1.
     
    Last edited: Apr 12, 2024
    tun1018 and mgear like this.
  12. tun1018

    tun1018

    Joined:
    Sep 18, 2019
    Posts:
    21
    WOW, thanks. GOOD Post.
     
    IgorAherneBusiness likes this.