经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
2.1:CGPROGRAM
来源:cnblogs  作者:Li_F  时间:2019/5/5 8:48:30  对本文有异议

文章著作权归作者所有。转载请联系作者,并在文中注明出处,给出原文链接。
本系列原更新于作者的github博客,这里给出链接

前言

经过前面两个章节的铺垫,我们对渲染以及Unity Shaderlab相关的知识已经有了大概的认识,接下来将要学习的就是Shader最重要的部分,SL(Shader Language),着色器语言。目前主流的着色器语言有HLSL,GLSL,Cg。三者在语法上也有诸多共通之处,选择一种学习即可。而在Unity中,主流是选择Cg作为着色器语言。在Shader编写的过程中,我们会经常穿梭在各个空间中,这里不对3D数学部分的前置知识作介绍,相关知识可从前面章节推荐的书籍学习。

在Shaderlab中,有三种着色器的书写方式。一种是Fixed-Function Shader,固定管线着色器。在这个着色器中,我们只能对渲染进行少量的配置,效果也很有限,在Unity 5.x之后的版本,Unity弃用了这种着色器。第二种是Surface Shader,表面着色器,这是Unity为我们提供的一种便于书写的方式,我们可以通过少量的代码,控制光照阴影等繁复的细节由Unity帮我们处理。新建一个Standard Surface Shader,可以看到里面只有50余行代码,但它包含了所有基础实现。最后一种,是Vertex/Fragment Shader,顶点/片元着色器,这是实现各种天马行空想象的最佳场所,当然,它的代码量以及复杂度也是最高的。而前两种shader也会被编译成对应的Vertex/Fragment Shader。这三种书写方式,都是在.shader文件中进行,组织方式上也是极为相似的。

这个系列的重点是Vertex/Fragment Shader。

CGPROGRAM

以之前模板的代码作为例子:

  1. Shader "Blog/Start" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4. _Color ("Color", Color) = (1, 1, 1, 1)
  5. }
  6. SubShader {
  7. CGINCLUDE
  8. #include "UnityCG.cginc"
  9. sampler2D _MainTex;
  10. float4 _MainTex_ST;
  11. fixed4 _Color;
  12. struct a2v {
  13. float4 vertex : POSITION;
  14. float2 texcoord : TEXCOORD0;
  15. };
  16. struct v2f {
  17. float4 pos : SV_POSITION;
  18. float2 uv : TEXCOORD0;
  19. };
  20. v2f vert(a2v v) {
  21. v2f o;
  22. o.pos = UnityObjectToClipPos(v.vertex);
  23. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  24. return o;
  25. }
  26. fixed4 frag(v2f i) : SV_Target {
  27. fixed4 color;
  28. color = tex2D(_MainTex, i.uv);
  29. color *= _Color;
  30. return color;
  31. }
  32. ENDCG
  33. Pass {
  34. CGPROGRAM
  35. #pragma vertex vert
  36. #pragma fragment frag
  37. ENDCG
  38. }
  39. }
  40. FallBack "Diffuse"
  41. }

在这个Shader中,出现了两个不同的代码块。首先第一个是CGINCLUDE代码块,它可以被放置在任何位置,甚至是整个Shader代码块的外部。在这个代码块中,我们可以编写那些需要重用的代码(如顶点着色器或片元着色器)。然后是CGPROGRAM代码块。这个代码块需要放在Pass块内,否则编译器会把这个Shader当成Surface Shader转而去检索surf()函数进而引起报错。这个代码块也是定义Vertex/Fragment Shader的地方。要保证,每个Pass都有且只有一个Vertex Shader和Fragment Shader。这两个Shader通过#pragma编译命令指定。接着是两个结构体:

  1. struct a2v {
  2. float4 vertex : POSITION;
  3. float2 texcoord : TEXCOORD0;
  4. };
  5. struct v2f {
  6. float4 pos : SV_POSITION;
  7. float2 uv : TEXCOORD0;
  8. };

第一个是顶点着色器的输入结构体,a2v即Application To Vertex(应用阶段到顶点着色),在每一个变量的后面都跟了一个冒号说明,冒号后的是这个变量的Semantic(语义),语义是和GPU通信的桥梁,告诉GPU在这个变量中填充什么数据。float4 vertex : POSITION;告诉GPU,把顶点数据的POSITION(模型空间下的顶点坐标)输入到vertex变量中,float2 texcoord : TEXCOORD0;的意思是,把纹理坐标集0给texcoord变量使用。在着色器之间的数据传递都是艺考语义实现的,使用结构体只是为了代码组织更有条理。

v2f即Vertex To Fragment,这是顶点着色器的输出结构体,也是片元着色器的输入结构体。float4 pos : SV_POSITION;:SV指System Value,带有SV前缀的语义在管线中都有特殊的含义,SV_POSITION的含义是裁剪空间下的坐标。

为什么输出裁剪空间下的顶点坐标?

因为这个坐标接下来用于片元着色器,片元着色器需要的是光栅化后的坐标,也就是裁剪空间的坐标。

然后是顶点着色器部分:

  1. v2f vert(a2v v) {
  2. v2f o;
  3. o.pos = UnityObjectToClipPos(v.vertex);
  4. o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
  5. return o;
  6. }

这里只是做里简单的空间变换以及纹理映射。既然需要的是裁剪空间的坐标,那直接把输入的顶点坐标变换到裁剪空间即可。UnityObjectToClipPos()是Unity为我们提供的坐标空间转换函数。

  1. // Tranforms position from object to homogenous space
  2. inline float4 UnityObjectToClipPos(in float3 pos)
  3. {
  4. #if defined(STEREO_CUBEMAP_RENDER_ON)
  5. return UnityObjectToClipPosODS(pos);
  6. #else
  7. // More efficient than computing M*VP matrix product
  8. return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
  9. #endif
  10. }
  11. inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
  12. {
  13. return UnityObjectToClipPos(pos.xyz);
  14. }

可以看到,这个函数处理了一些差别并重载了两个版本,但本质上,都是MVP矩阵右乘顶点坐标的列向量形式。然后是TRANSFORM_TEX宏。

  1. // Transforms 2D UV by scale/bias property
  2. #define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

可以看到这个宏计算了顶点对应的纹理采样位置,计算方式也对应了我们之前说到的_ST(Scale & Tiling,纹理缩放和偏移)相关知识点。然后是片元着色器部分:

  1. fixed4 frag(v2f i) : SV_Target {
  2. fixed4 color;
  3. color = tex2D(_MainTex, i.uv);
  4. color *= _Color;
  5. return color;
  6. }

片元着色器的最终目的是确定片元的像素颜色,即一个RGBA值。首先注意到的是SV_Target,它的语义是:这个着色器只返回一个值,这个值也就是片元的像素颜色值。此外,片元着色器还可以返回多个颜色,这时我们需要用到SV_TargetN语义,在这个情境下,SV_Target0是对应片元的像素颜色。例:

  1. struct frag_output {
  2. fixed4 color0 : SV_Target0;
  3. fixed4 color1 : SV_Target1;
  4. fixed4 color2 : SV_Target2;
  5. }
  6. frag_output frag(v2f i) {
  7. frag_output output;
  8. // ...
  9. return output;
  10. }

回到模板的片元着色器内部,首先是color = tex2D(_MainTex, i.uv);tex2D(sampler2D texture, float2 uv);是Cg为我们提供的一个纹理采样函数,它将按照输入的uv采样输入的纹理texture,最后返回采样颜色。然后是color *= _Color;这里只是简单的把采样颜色和shader外部给定的颜色做乘法处理(叠加)。在场景中新建一个sphere,把这个shader添加到一个新的material,再把这个material挂到sphere上,不出意外没有报错的话,即可得到一个纯白色的球(由于没有任何光照阴影计算,也没有给纹理赋值)。这是我们第一个生效的shader。

Sphere for First Shader

原文链接:http://www.cnblogs.com/Li-F/p/10806798.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号