Koo叔说Shader—最基本的Shader

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/java3182/article/details/80748798

写在前面

看了许多Shader教程,也学到了一些常见的Shader效果,但还是有一些迷糊,不能灵活的运用。所以打算一步步从零开始,把Shader彻底搞明白。以此记录一下学习过程。

Shader效果

  • 单个对象
  • 正确渲染出对象
  • 将对象设置成黄色(为啥是黄色,随便选的颜色:-))

思路

  • 这里选择Quad作为渲染对象,顶点组成比较简单
  • 要正确显示出Quad,需要将顶点位置告诉GPU
  • 要正确显示黄色,则要告诉GPU一个黄色的色值

以下是具体细节实现的过程,我们一步步来:

先建一个新的项目

目录结构如下:

这里写图片描述

先从最简单的开始,后期可能会整理,Materials主要存放材质,Prefabs存放实验用的prefab,Textures主要是贴图资源,Loading是测试用的场景,Shaders里放实验用的Shader

新建测试对象

  • 选择GameObject->3D Object->Quad,新建一个Quad对象
  • 新建一个Material,替换掉默认的Material
  • 新建一个Shader,替换掉默认的Shader
  • 将Shader中自动生成的内容清空,然后一步步的添加需要的内容,清空后的Shader内容如下:
Shader "Unlit/first"{

}

这时的场景显示如下:
这里写图片描述
为什么会显示一个粉色呢?

选中Shader后,查看Inspector面板发现,原来有一个错误,Unity发现Shader是一个空的,所以不能编译,就给Quad对象一个默认的粉色来提醒(实际上是用了一个默认的Shader)

选中shader后在Inspector面板查看如下:

这里写图片描述
会出现一个Errors,提示没有subshaders或默认的fallbacks支持GPU,也没有生成编译后的Shader代码。

试试加上属性会是什么结果:

Shader "Unlit/first"{
    Properties{
        _MainTex("Texture 2D",2D) = "white"{}
    }
}

结果还是会出现如上警告

如果加上SubShader不加属性呢:

Shader "Unlit/first"{
    SubShader{

    }
}

这时会出现一个“==Shader error in ‘first’: Parse error: syntax error, unexpected ‘}’ at line 4==”error,说明只有一个空的SubShader是不行的。

加上Pass试试:

Shader "Unlit/first"{
    SubShader{
        Pass{

        }
    }
}

此时编译通过了,并且Quad对象也不是显示粉色了。而是显示了一个默认的白色,Shader的Inspector也显示了Show generated code和Compile and show code两项:
这里写图片描述
说明Unity中的Shader最少需要一个SubShader加一个Pass

**如果Pass中没有CGPROGRAM … ENDCG段,则Unity会自动生成一段默认的Shader,点击”Show generated code”,可以查看其中的内容

如果有CGPROGRAM … ENDCG段,Unity就不自动生成Shader代码了,需要自己指定vertex和fragment函数,如果没有指定会生成一个编译错误。”Compile and show code”是Unity中的Shader编译成glsl等图形api的shader,可以查看其中生成的最底层的内容,以方便查错和探究其中的细节。这里先不展开说这个。**

经过多次实验,Shader中应该有如下内容才能开始实现自己的Vertex Shader和Fragment Shader:

Shader "Unlit/first"{
    SubShader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct appdata{

            };
            struct v2f{

            };
            v2f vert(appdata IN){

            }
            fixed4 frag(v2f IN):SV_TARGET{
                return fixed(1,1,0,1);
            }
            ENDCG
        }
    }
}
  • #pragma vertex vert和#pragma fragment frag指定vertex和fragment要调用哪个函数,这里是vert和frag函数
  • struct appdata是vert函数的输入
  • struct v2f是vert函数的输出也是frag函数的输入
  • SV_TARGET是绑定语义,绑定颜色缓存

    上面这段代码中,虽然vert是空的,实际测试是可以编译通过,不报错的,而frag中必须要return 一个颜色值,并且SV_TARGET也是要指定的,否则都会报错

    从上面代码的逻辑来看是给了一个黄颜色的值,是不是我们的Shader效果算完成了呢,到场景里看一下

结果,这段shader作用到Quad对象后,Quad对象消失了,场景里空空如也,为什么呢?

原因很简单,没有指定位置嘛,你不告诉位置,GPU怎么知道把对象放哪里呢,虽然不给位置代码能编译通过,但至少要给一个位置信息,才能把对象正确的显示出来。修改后的Shader代码如下:

Shader "Unlit/first"{
    SubShader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct appdata{
                float4 pos:POSITION;
            };
            struct v2f{
                float4 pos:SV_POSITION;
            };
            v2f vert(appdata IN){
                v2f o;
                o.pos = IN.pos;
                return o;
            }
            fixed4 frag(v2f IN):SV_TARGET{
                return fixed4(1,1,0,1);
            }
            ENDCG
        }
    }
}

这次会不会显示正确了呢?
显示效果如下:
这里写图片描述
这次显示出来了,但总感觉哪里不对劲,不是应该是正方形的吗?怎么变长方形了,不是应该和红线重合吗?怎么对不上了。而且移动相机,改变Quad对象的transform的值,这个黄色块块位置也不会动。
这个显示结果显然不是我们想要的,预期的结果应该是一个正方形,而且能随着相机和物体的坐标改变位置。

是哪里出问题了呢?
- 这就要熟悉渲染流水线了,由于在vertex shader中没有对坐标做任何处理,之后的光栅化阶段中做视口变换时,会认为拿到的坐标已经是标准化设备的坐标了(标准化设备坐标就是一个x,y,z值在-1.0到1.0的一小段空间。落在范围外的坐标都会被丢弃/裁剪),所以直接对顶点值做了透视除法视口变换,和剔除,因为Quad对象的顶点是由{-0.5,-0.5},{0.5,0.5},{0.5,-0.5},{-0.5,0.5}四个顶点组成,是固定不变的,所以如果不在vertex shader中做处理,改变guad的位置,移动相机,等变换操作都不会改变显示结果,只有相机的Viewport Rect的值会影响视口的变化,视口变换时也是读取的这个值
- Unity中对象的mesh顶点信息,实际上是相对于gameobject的局部坐标系来组织的,所以顶点信息必须要转成世界坐标才能显示正确。
- 如果跟随相机变化,那就需要转成相机空间。

结论:是因为顶点没有在vertex Shader中做任何处理,流入到光栅阶段后,被当成了标准化设备坐标进行了透视除法,视口变换和剔除操作,所以导致了错误的显示结果

通过上面的分析,得到,要想在Unity中显示正确的结果,就需要将顶点坐标转成世界坐标,世界坐标转成相机空间坐标,将相机空间坐标通过投影变换裁剪,变成标准化设备坐标(NDC),这个过程就是常说的流水线中的模型变换,相机变换,和投影裁剪变换,这些是通过矩阵表示来计算的,叫做MVP矩阵,这些矩阵不在这里展开说,可以查其他相关资料,计算后的结果再流入下一阶段就可以了。

Unity将一些常用操作,封装到了”UnityCG.cginc”中,上述变换操作可以用UnityObjectToClipPos()实现,里面其实就是MVP矩阵,修改后的vertex shader如下:

Shader "Unlit/first"{
    SubShader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata{
                float4 pos:POSITION;
            };
            struct v2f{
                float4 pos:SV_POSITION;
            };
            v2f vert(appdata IN){
                v2f o;
                o.pos = UnityObjectToClipPos(IN.pos);
                return o;
            }
            fixed4 frag(v2f IN):SV_TARGET{
                return fixed4(1,1,0,1);
            }
            ENDCG
        }
    }
}

效果如下:

这里写图片描述

这次得到了想要的效果,也可以随相机和物体的变化而变化 了。

总结

今天实现了一个最基本的Shader,显示位置并设置颜色。有了这个基础,接下来,一步步向更高级Shader,如需配套代码,可以从作者github获取

猜你喜欢

转载自blog.csdn.net/java3182/article/details/80748798
今日推荐