標籤:

3DLUT的unity實現

3DLUT的unity實現

來自專欄 Unity Graphics10 人贊了文章

在做項目時同事跟我說了一個LUT的概念,然後一臉懵逼,之前也是做濾鏡什麼的,但是完全沒用到過這種技術,然後就深入的研究了一下實現方式,真是發現了一片新天地。

1.LUT定義

顯示查找表(Look-Up-Table)簡稱為LUT

LUT實際上就是一張像素灰度值的映射表,它將實際採樣到的像素灰度值經過一定的變換如閾值、反轉、二值化、對比度調整、線性變換等,變成了另外一個與之對應的灰度值,這樣可以起到突出圖像的有用信息,增強圖像的光對比度的作用。

3D-LUT:從本質上來說,LUT的作用就是將每一組RGB的輸入值轉化成輸出值。

2.用途:

校準LUT

校準LUT是用來「修正」顯示器不準確的地方,它能夠確保經過校準的顯示器可以顯示儘可能準確的圖像——在顯示器的能力限制範圍之內。

技術LUT

技術LUT用於轉換不同的「標準」,比如說從一個顏色空間轉換到另一個。

創意LUT

用於風格化,濾鏡等,此處運用的3DLUT就是實現unity濾鏡效果。

濾鏡示例:

輸入:

輸出:

效果:

3.解讀:

幾何解讀:3DLUT 可以看成是一個記錄了色域範圍內關鍵點顏色點坐標的立方體。

我更願意把它看作是B分量不同灰度值上關於R,G分量分布的切片圖組合,這樣更容易理解。

下圖是我根據製作的一張簡易LUT 2*2的圖,為了驗證原理,RGB分量都是線性分布(實際上RGB分量在不同的顯示系統裡面會根據顯示效果和色域做校正,因此一般不是線性分布):

輸入:將原始色彩空間映射到lut圖。

先根據B值找到對應灰度的切片,然後根據R,G值找到在此切片上的坐標,得到顏色值在立方體的坐標。不在關鍵點的顏色坐標,通過插值得到。

操作:將顏色變化應用到切片圖。

輸出:按照原來顏色的坐標,找到坐標位置替換後的顏色,替換原有的顏色。RG分量上面根據UV坐標位置對顏色進行插值,B分量上面根據兩個切片和所處兩個切片中間的坐標位置進行插值

8*8的lut,採樣解析度為64。

參考鏈接:

濾鏡-Color Lookup Table(ColorLUT)技術?

blog.csdn.net圖標

4.GLSL代碼

8*8 LUT原圖:

8*8 LUT代碼:

varying highp vec2 textureCoordinate;uniform sampler2D inputImageTexture;uniform sampler2D inputImageTexture2;uniform lowp float intensity;void main() { highp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate); highp float blueColor = textureColor.b * 63.0; highp vec2 quad1; quad1.y = floor(floor(blueColor) / 8.0); quad1.x = floor(blueColor) - (quad1.y * 8.0); highp vec2 quad2; quad2.y = floor(ceil(blueColor) / 8.0); quad2.x = ceil(blueColor) - (quad2.y * 8.0); highp vec2 texPos1; texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); highp vec2 texPos2; texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); lowp vec4 newColor1 = texture2D(inputImageTexture2, texPos1); lowp vec4 newColor2 = texture2D(inputImageTexture2, texPos2); lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); gl_FragColor = vec4(newColor.rgb, textureColor.w);}

--------------------------------------------------------

另外一種計算方式:

#version 300 es#ifdef GL_ES//for discriminate GLES & GL#ifdef GL_FRAGMENT_PRECISION_HIGHprecision highp float;#elseprecision mediump float;#endif#else#define highp#define mediump#define lowp#endifin vec2 texcoordOut;uniform sampler2D colorMap;uniform sampler2D mainTexture;out vec4 color;void main(){ const float EPS = 0.000001; const float pxSize = 512.0; vec3 orgColor = texture(mainTexture, texcoordOut).rgb; float bValue = (orgColor.b * 255.0) / 4.0; vec2 mulB = clamp(floor(bValue) + vec2(0.0, 1.0), 0.0, 63.0); vec2 row = floor(mulB / 8.0 + EPS); vec4 row_col = vec4(row, mulB - row * 8.0); vec4 lookup = orgColor.ggrr * (63.0/pxSize) + row_col * (64.0/pxSize) + (0.5/pxSize); float b1w = bValue - mulB.x; vec3 sampled1 = texture(colorMap, lookup.zx).rgb; vec3 sampled2 = texture(colorMap, lookup.wy).rgb; vec3 res = mix(sampled1, sampled2, b1w); color = vec4(res, 1.0);}

5.unity實現

8*8lut代碼:

Shader "chain/LUT64"{ Properties { _MainTex ("Texture", 2D) = "white" {} _LutTex("FilterMap",2D)="white"{} } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float2 uv : TEXCOORD0; float4 pos : SV_POSITION; }; v2f vert (appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; return o; } sampler2D _MainTex; sampler2D _LutTex; fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv);//B分量分為64個片段 float Bcolor = col.b * 63; float2 quad1;//獲取豎向和橫向的片段位置(共獲取2個B值片段位置,並在之後用餘數插值) quad1.y = floor(floor(Bcolor)/8); quad1.x = floor(Bcolor)-(quad1.y*8); float2 quad2; quad2.y = ceil(floor(Bcolor)/8); quad2.x = ceil(Bcolor)-(quad2.y*8); float2 uv1; float2 uv2; //計算UV坐標,並對採樣範圍左右各縮小0.5 uv1.x = ((quad1.x)*0.125)+ 0.5/512.0 +((0.125-0.5/512.0)* col.r); uv1.y =1-(((quad1.y)*0.125) + 0.5/512.0 +((0.125-0.5/512.0)* col.g)); uv2.x = ((quad2.x)*0.125)+ 0.5/512.0 +((0.125-0.5/512.0)* col.r); uv2.y = 1-(((quad2.y)*0.125)+ 0.5/512.0 +((0.125-0.5/512.0)* col.g)); //採樣 fixed4 col1 = tex2D(_LutTex,uv1); fixed4 col2 = tex2D(_LutTex,uv2); //根據B值的灰度插值 col.rgb = lerp(col1.rgb,col2.rgb, frac(Bcolor)); col=fixed4(col.rgb,1.0); return col; } ENDCG } }}

2*2 LUT片元代碼,實際上只是把對應行列數修改了一下:

fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); float Bcolor = col.b * 3; float2 quad1; quad1.y = floor(floor(Bcolor)/2); quad1.x = floor(Bcolor)-(quad1.y*2); float2 quad2; quad2.y = ceil(floor(Bcolor)/2); quad2.x = ceil(Bcolor)-(quad2.y*2); float2 uv1; float2 uv2; uv1.x = ((quad1.x)*0.5)+1/512+ (0.5-0.5/512)* col.r; uv1.y =1-(((quad1.y)*0.5) + 1/512+ (0.5-0.5/512)* col.g); uv2.x = ((quad2.x)*0.5)+1/512+ (0.5-0.5/512)* col.r; uv2.y = 1-(((quad2.y)*0.5)+ 1/512+ (0.5-0.5/512)* col.g); fixed4 col1 = tex2D(_LutTex,uv1); fixed4 col2 = tex2D(_LutTex,uv2); col.rgb = lerp(col1.rgb,col2.rgb, frac(Bcolor)); col=fixed4(col.rgb,1.0); return col; }

實際實現的時候發現在uv計算的時候不能單純的除以行列數,如果這樣做的話會發現畫面在高亮的位置出現白點或者黑點的情況,這是因為圖片採樣的時候並不是100%的精確,浮點的計算總會造成誤差,再加上圖片質量的損失,在邊緣的地方採樣會造成UV溢出,所以需要進行一點點的修正,縮小採樣的範圍,關鍵代碼就是這兩句:

註:圖片質量設置中,將SRGB選項關閉,用以避免Unity的色彩空間轉換。

必須將Generate Mip Map選項關閉,以避免圖片質量損失,造成效果損失。

5.photoshop完成LUT濾鏡圖片的製作

1.直接對初始的顏色二維圖使用LUT效果,存儲完成圖作為濾鏡查找圖

教程參考:

!!! Photoshop CS6鮮為人知的一個調色功能LUT?

www.360doc.com圖標

2.使用動作記錄對圖片的調整,然後對初始顏色二維圖播放動作,存儲完成圖作為濾鏡查找圖

未探索的點:

  1. 色彩空間和LUT的映射關係
  2. LUT原圖生成方式
  3. 用二進位文本格式存儲的圖片不會存在質量損失的問題,也不會存在採樣過界的問題,未來可以考慮直接讀取.cube等LUT格式的文件以替代LUT圖片

後記:unity的postprocessing也有自帶的LUT濾鏡,有興趣可以研究下。除了用作濾鏡調色之外,在皮膚渲染次表面散射用到的LUT圖,和這裡的lut原理也是類似的。


推薦閱讀:

Community of inquiry model(From EduTech Wiki)
unity自定義材質面板的一個小心得
Unity3D優化專題
騙關預報——膚質渲染與膚質上軟陰影

TAG:unity | lut | 濾鏡 |