Linux OpenGL 實踐篇-12-ProceduralTexturing
程序式紋理
簡單的來說程序式紋理就是用數學公式描述物體表面的紋路 。而實現這個過程的著色器我們稱之為程序紋理著色器,通常在這類著色器中我們能使用的輸入信息也就是頂點坐標和紋理坐標。
程序式紋理的優點
1.程序式紋理的內存占用比預存紋理要低的多;因為程序式紋理主要是算法的實現,數據都是通過計算產生的;
2.程序生成的紋理沒有固定的面積和分辨率,可以隨意的應用到不同大小的物體,而不用擔心精度不夠的問題;
3.程序式紋理可以寫入一些算法的關鍵參數,可以方便的供程序修改從而創建出有趣的效果,而預存的紋理則很難進行改動;
4.如果我們使用程序式紋理計算體積而不是表面的話,那麽體積的剖面表現力會比任何使用2維紋理的方式要真實的多。
程序式紋理缺點
1.程序式紋理對編程人員的要求較高,特別是算法要求;
2.程序式紋理的數據是實時計算產生的,所以每一次都需要重新計算,相對預存紋理時間會花費的比較多;
3.程序式紋理可能會帶來一些難以克服的走樣問題;而對於預存紋理現在的圖形硬件都有比較成熟的反走樣算法;
4.由於數學精度上的差異和噪聲實現算法上的差異,在不同平臺上程序式紋理的表現不一定一致。
簡單的程序式紋理
磚塊
頂點著色器:
#version 330 core layout(location=0) in vec3 iPos; layout(location=1) in vec2 iTexcoord; uniform mat4 model; uniform mat4 view; uniform mat4 proj; out vec2 texcoord; out vec2 mcPos; void main() { texcoord = iTexcoord; mcPos = iPos.xy; gl_Position = proj * view * model * vec4(iPos,1.0); }
片元著色器:
#version 330 corein vec2 texcoord; in vec2 mcPos; out vec4 color; uniform vec3 brickColor,mortarColor; uniform vec2 brickSize; uniform vec2 brickPct; void main() { vec2 pos, useBrick; pos = mcPos / brickSize; if(fract(pos.y * 0.5) > 0.5) { pos.x += 0.5 ; } pos = fract(pos); useBrick = step(pos,brickPct); vec3 c = mix(mortarColor,brickColor,useBrick.x * useBrick.y); color = vec4(c,1.0); }
效果圖:
晶格
頂點著色器:
#version 330 core layout(location=0) in vec3 iPos; layout(location=1) in vec2 iTexcoord; uniform mat4 model; uniform mat4 view; uniform mat4 proj; out vec2 texcoord; void main() { texcoord = iTexcoord; gl_Position = proj * view * model * vec4(iPos,1.0); }
片元著色器:
#version 330 core in vec2 texcoord; out vec4 color; uniform vec2 scale; uniform vec2 threshold; void main() { float ss = fract(texcoord.s * scale.s); float tt = fract(texcoord.t * scale.t); if((ss > threshold.s) && (tt > threshold.t)) discard; color = vec4(1,0,0,1); }
效果圖:
噪聲
使用計算機渲染精致的物體式非常的容易的,但在真實的世界中物體往往不是這樣的,它們經常是帶汙漬、凹痕、磨損的,如果要實現這樣的效果,藝術家通常要花費很多的時間來進行構建。針對這個問題,Ken Perlin在20世紀80年代進行了深入的研究,提出了一種直到現在也很有用的技術——噪聲。噪聲我們可以認為是一些無規律的數據,類似老電視機中沒有信號時出現的隨機的雪花像素點。但這種隨機的數據對於計算機圖形學並沒有什麽用處,在計算機圖形學當中,我們需要的是一種可重復的函數。比如,對於某個物體的表面,我們希望隨機分布是空間上的,而不是時間上的,除非有特定的需求。根據以上的需求,理想的噪聲函數應該具備下面一些重要的特性:
1.噪聲不會有任何明顯的規則或者重復花樣;
2.噪聲是一個連續函數,它的導數也是連續的;
3.噪聲函數的結果可以隨時間變化復現(也就是說,每一次輸入的數據一致時,它返回的值也是相同的)。
4.噪聲的輸出數據需要一個明確的空間定義(通常是[-1,1]或[0,1]);
5.噪聲函數的小規模形式不會受到大範圍的位置數據影響;
6.噪聲函數是各向同性的(它的統計特性在所有的方向都是相同的);
7.噪聲可以定義為1、2、3、4或者更高維度;
8.對於任何給定的輸入,噪聲的計算都是非常迅速。
在OpenGL中使用以下三種方式為程序添加噪聲:
1.自己實現noise函數;
2.使用內置OpenGL函數noise實現;
3.使用紋理預存噪聲數據;
下面是自己實現的一個 perlin噪聲函數:
/* coherent noise function over 1, 2 or 3 dimensions */ /* (copyright Ken Perlin) */ #include <stdlib.h> #include <stdio.h> #include <math.h> #define B 0x100 #define BM 0xff #define N 0x1000 #define NP 12 /* 2^N */ #define NM 0xfff static int p[B + B + 2]; static float g3[B + B + 2][3]; static float g2[B + B + 2][2]; static float g1[B + B + 2]; static int start = 1; static void init(void); #define s_curve(t) ( t * t * (3. - 2. * t) ) #define lerp(t, a, b) ( a + t * (b - a) ) #define setup(i,b0,b1,r0,r1)\ t = vec[i] + N;b0 = ((int)t) & BM;b1 = (b0+1) & BM;r0 = t - (int)t;r1 = r0 - 1.; double noise1(double arg) { int bx0, bx1; float rx0, rx1, sx, t, u, v, vec[1]; vec[0] = arg; +--- 4 行: if (start) {------------------------------------------------ setup(0, bx0,bx1, rx0,rx1); sx = s_curve(rx0); u = rx0 * g1[ p[ bx0 ] ]; v = rx1 * g1[ p[ bx1 ] ]; return lerp(sx, u, v); } float noise2(float vec[2]) { int bx0, bx1, by0, by1, b00, b10, b01, b11; float rx0, rx1, ry0, ry1, *q, sx, sy, a, b, t, u, v; register int i, j; +--- 4 行: if (start) {---------------------------------------------------------------------- setup(0, bx0,bx1, rx0,rx1); setup(1, by0,by1, ry0,ry1); i = p[ bx0 ]; j = p[ bx1 ]; b00 = p[ i + by0 ]; b10 = p[ j + by0 ]; b01 = p[ i + by1 ]; b11 = p[ j + by1 ]; sx = s_curve(rx0); sy = s_curve(ry0); #define at2(rx,ry) ( rx * q[0] + ry * q[1] ) q = g2[ b00 ] ; u = at2(rx0,ry0); q = g2[ b10 ] ; v = at2(rx1,ry0); a = lerp(sx, u, v); q = g2[ b01 ] ; u = at2(rx0,ry1); q = g2[ b11 ] ; v = at2(rx1,ry1); b = lerp(sx, u, v); return lerp(sy, a, b); } float noise3(float vec[3]) { int bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11; float rx0, rx1, ry0, ry1, rz0, rz1, *q, sy, sz, a, b, c, d, t, u, v; register int i, j; +--- 4 行: if (start) {------------------------------------------------------------ setup(0, bx0,bx1, rx0,rx1); setup(1, by0,by1, ry0,ry1); setup(2, bz0,bz1, rz0,rz1); i = p[ bx0 ]; j = p[ bx1 ]; b00 = p[ i + by0 ]; b10 = p[ j + by0 ]; b01 = p[ i + by1 ]; b11 = p[ j + by1 ]; t = s_curve(rx0); sy = s_curve(ry0); sz = s_curve(rz0); #define at3(rx,ry,rz) ( rx * q[0] + ry * q[1] + rz * q[2] ) q = g3[ b00 + bz0 ] ; u = at3(rx0,ry0,rz0); q = g3[ b10 + bz0 ] ; v = at3(rx1,ry0,rz0); a = lerp(t, u, v); q = g3[ b01 + bz0 ] ; u = at3(rx0,ry1,rz0); q = g3[ b11 + bz0 ] ; v = at3(rx1,ry1,rz0); b = lerp(t, u, v); c = lerp(sy, a, b); q = g3[ b00 + bz1 ] ; u = at3(rx0,ry0,rz1); q = g3[ b10 + bz1 ] ; v = at3(rx1,ry0,rz1); a = lerp(t, u, v); q = g3[ b01 + bz1 ] ; u = at3(rx0,ry1,rz1); q = g3[ b11 + bz1 ] ; v = at3(rx1,ry1,rz1); b = lerp(t, u, v); d = lerp(sy, a, b); return lerp(sz, c, d); } static void normalize2(float v[2]) { float s; s = sqrt(v[0] * v[0] + v[1] * v[1]); v[0] = v[0] / s; v[1] = v[1] / s; } static void normalize3(float v[3]) { float s; s = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); v[0] = v[0] / s; v[1] = v[1] / s; v[2] = v[2] / s; } static void init(void) { int i, j, k; for (i = 0 ; i < B ; i++) { p[i] = i; g1[i] = (float)((random() % (B + B)) - B) / B; for (j = 0 ; j < 2 ; j++) g2[i][j] = (float)((random() % (B + B)) - B) / B; normalize2(g2[i]); for (j = 0 ; j < 3 ; j++) g3[i][j] = (float)((random() % (B + B)) - B) / B; normalize3(g3[i]); } while (--i) { k = p[i]; p[i] = p[j = random() % B]; p[j] = k; } for (i = 0 ; i < B + 2 ; i++) { p[B + i] = p[i]; g1[B + i] = g1[i]; for (j = 0 ; j < 2 ; j++) g2[B + i][j] = g2[i][j]; for (j = 0 ; j < 3 ; j++) g3[B + i][j] = g3[i][j]; } }
頂點著色器:
#version 330 core uniform mat4 MVMat; uniform mat4 MVPMat; uniform mat4 normalMat; uniform vec3 lightPos; uniform float scale; layout(location = 0)in vec3 iPos; layout(location = 2)in vec3 iNormal; out float lightIntensity; out vec3 mcPos; void main() { vec3 ecPos = vec3(MVMat * vec4(iPos,1)); mcPos = iPos * scale; vec3 tnorm = normalize(vec3(normalMat * vec4(iNormal,1.0))); vec3 lpos = vec3(MVMat * vec4(lightPos,1.0)); lightIntensity = dot(normalize(lpos - ecPos),tnorm); lightIntensity *= 1.5f; gl_Position = MVPMat * vec4(iPos,1); }
片元著色器:
#version 330 core uniform sampler3D noise; uniform vec3 skyColor; uniform vec3 cloudColor; in float lightIntensity; in vec3 mcPos; out vec4 color; void main() { vec4 noisevec = texture(noise,mcPos); float intensity = (noisevec[0] + noisevec[1] + noisevec[2] + noisevec[3] + 0.03125) * 1.5; vec3 c = mix(skyColor,cloudColor,intensity) * lightIntensity; color = vec4(c,1.0); }
效果圖:
實踐源代碼:https://github.com/xin-lover/opengl-learn/tree/master/chapter-12-procedural_texturing
Linux OpenGL 實踐篇-12-ProceduralTexturing