GLSL Programming/Unity/RGB Cube

維基教科書,自由的教學讀本
一個RGB 立方體: x,y,z坐標映射到紅綠藍顏色分量上.

本教程介紹 易變變量,以 Minimal Shader 為基礎.

本教程我們將寫一個shader來將立方體渲染成上圖的樣子.表面上每個點的顏色由它的坐標來決定; 比如,位置的點顏色為. 舉例來說,點 被映射為,是純藍色.(就是圖右下角的藍色拐角).

準備[編輯]

因為我們要建立RGB立方體,首先需要建立一個立方體.跟Minimal Shader的球體類似,你可以通過菜單GameObject > Create Other > Cube來建立立方體.接著創建一個材質和一個shader並將shader附加到材質,材質附加給立方體.

Shader 代碼[編輯]

這裡是shader代碼,你可以直接複製到你的shader中:

Shader "GLSL shader for RGB cube" {
   SubShader {
      Pass {
         GLSLPROGRAM
         
         #ifdef VERTEX // 这里开始顶点着色器
         
         varying vec4 position; 
            // 这是顶点着色器中的一个易变变量.
         
         void main()
         {
            position = gl_Vertex + vec4(0.5, 0.5, 0.5, 0.0);
               // 这里,顶点着色器输出数据到易变变量.
               // 我们将x,y,z各加了0.5,因为立方体的顶点坐标在-0.5和0.5间,但我们需要它们在0.0到1.0之间.
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif // 结束顶点着色器

         #ifdef FRAGMENT // 这里开始片段着色器
         
         varying vec4 position; 
            // 这里是片段着色器中一个易变变量.
         
         void main()
         {
            gl_FragColor = position;
               // 这里,片段着色器从易变变量中读取输入数据.
               // 将易变变量的值,赋值给片段颜色的rgba
         }
         
         #endif // 这里介绍片段着色器

         ENDGLSL
      }
   }
}

如果你的立方體顏色不對,仔細檢查console的錯誤信息(點擊菜單Window > Console),確保shader已經保存,並檢查你的shader是否附加到材質上,材質是否附加到遊戲物體上.

易變變量[編輯]

片段shader重要的任務是設置輸出片段的顏色(gl_FragColor),頂點著色器中位置(gl_Vertex)可用. 實際上,這不完全準確:Unity默認立方體的坐標gl_Vertex是-0.5到0.5,我們的顏色分量範圍是0.0到1.0,為此,我們需要將x,y,z各加上0.5,通過式子:gl_Vertex+vec4(0.5,0.5,0.5,0.0).

最主要的問題是,如何從頂點shader傳遞數據給片段shader? 結果是只有一種方式來完成此目的就是使用易變變量(簡稱易變).頂點shader的輸出可以寫入易變變量之後片段shader可以作為輸入來讀取.這恰恰是我們需要的.

為了指定一個易變變量,需要在頂點和片段shader的方法外,用位於類型前的修飾符varying來定義;我們的例子中:varying vec4 position;. 易變變量最重要的規則: 頂點shader中定義的易變變量的類型和名稱必須與片段shader中定義的易變變量完全一致,反之亦然

這裡需要避免引起歧義的情況,這樣GLSL編譯器就無法找出頂點shader與片段shader對應的易變變量.

Unity中易變變量的技巧[編輯]

頂點shader和片段shader的易變變量需要保持一致,經常會出現錯誤,如一位程式設計師改變了頂點shader的易變變量但忘記更新片段shader.幸運的是,Unity中有一個很好的技巧來避免這個問題. 看下面的shader:

Shader "GLSL shader for RGB cube" {
   SubShader {
      Pass {
         GLSLPROGRAM // 开始顶点和片段shader
         
         varying vec4 position; 
            // 此行是顶点和片段shader共有的部分.
         
         #ifdef VERTEX 
            // 这里仅仅是顶点shader部分
         
         void main()
         {
            position = gl_Vertex + vec4(0.5, 0.5, 0.5, 0.0);
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif 
            // 顶点shader部分结束

         #ifdef FRAGMENT 
            // 这里仅是片段shader部分
         
         void main()
         {
            gl_FragColor = position;
         }
         
         #endif 
            // 介绍片段shader独有的部分

         ENDGLSL // 结束顶点和片段shader共有部分
      }
   }
}

正如shader注釋的那樣,行#ifdef VERTEX不是標識是頂點shader的開始,而是開始只有頂點shader的那部分. 類似,#ifdef FRAGMENT標識出僅是片段shader的那部分.實際上,shader都(vs,fs)是從GLSLPROGRAM開始. 因此,GLSLPROGRAM間的代碼以及#ifdef前幾行被頂點和片段shader所共有.(如果你知道c,c++與編譯器,你可能早已想到).

這可以完美的定義易變變量,因為我們只需定義一次,之後就被分別放入頂點和片段shader了;這樣就確保了定義的匹配!我們可以輸入更少並且不會產生易變變量不匹配的錯誤.(當然,代價是我們不得不輸入所有的#ifdef#end).

對該Shader的變換[編輯]

The RGB cube represents the set of available colors (i.e. the gamut of the display). Thus, it can also be used show the effect of a color transformation. For example, a color to gray transformation would compute either the mean of the red, green, and blue color components, i.e. , and then put this value in all three color components of the fragment color to obtain a gray value of the same intensity. Instead of the mean, the relative luminance could also be used, which is . Of course, any other color transformation (changing saturation, contrast, hue, etc.) is also applicable.

Another variation of this shader could compute a CMY (cyan, magenta, yellow) cube: for position you could subtract from a pure white an amount of red that is proportional to in order to produce cyan. Furthermore, you would subtract an amount of green in proportion to the component to produce magenta and also an amount of blue in proportion to to produce yellow.

If you really want to get fancy, you could compute an HSV (hue, saturation, value) cylinder. For and coordinates between -0.5 and +0.5, you can get an angle between 0 and 360° with 180.0+degrees(atan(z, x)) in GLSL and a distance between 0 and 1 from the axis with 2.0 * sqrt(x * x + z * z). The coordinate for Unity's built-in cylinder is between -1 and 1 which can be translated to a value between 0 and 1 by . The computation of RGB colors from HSV coordinates is described in the article on HSV in Wikipedia.

Interpolation of Varying Variables[編輯]

The story about varying variables is not quite over yet. If you select the cube game object, you will see in the Scene View that it consists of only 12 triangles and 8 vertices. Thus, the vertex shader might be called only eight times and only eight different outputs are written to the varying variable. However, there are many more colors on the cube. How did that happen?

The answer is implied by the name varying variables. They are called this way because they vary across a triangle. In fact, the vertex shader is only called for each vertex of each triangle. If the vertex shader writes different values to a varying variable for different vertices, the values are interpolated across the triangle. The fragment shader is then called for each pixel that is covered by the triangle and receives interpolated values of the varying variables. The details of this interpolation are described in Template:GLSL Programming Unity SectionRef.

If you want to make sure that a fragment shader receives one exact, non-interpolated value by a vertex shader, you have to make sure that the vertex shader writes the same value to the varying variable for all vertices of a triangle.

Summary[編輯]

And this is the end of this tutorial. Congratulations! Among other things, you have seen:

  • What an RGB cube is.
  • What varying variables are good for and how to define them.
  • How to make sure that a varying variable has the same name and type in the vertex shader and the fragment shader.
  • How the values written to a varying variable by the vertex shader are interpolated across a triangle before they are received by the fragment shader.

Further Reading[編輯]

If you want to know more


Template:GLSL Programming BottomNav