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