分类目录归档:未分类

Shader入门——2贴图材质

本节我们进行材质上最基本的贴图。也就是最常用的贴图材质编写。首先是unity shader的代码编写。

Shader"Custom/BaseTextureMatShader"
{
    Properties{
        _Color("Color Tint",Color)=(1.0,1.0,1.0,1.0)
        _MainTex("Main Texture",2D)="white"
    }
    Subshader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragmengt frag
            fixed4 _Color;
            sampler2D _MainTex;
            
            float4 vert(float4 pos:POSITION):SV_POSITION{
                return UnityObjectToClipPos(pos);
            }

            fixed4 frag(float2 uv:TEXCOORD0):SV_Target{
                return tex2D(_MainTex,uv);
             }
            
            ENDCG
        }
    }
    Fallback "Diffuse"
}

我们继续来逐行翻译,但也不必像之前那样逐行,我们只翻译类比上一篇代码中的不同部分。首先我们在Properties下增加了_MainTex这个属性,他的面板名称为“Main Tex”,属性是2D贴图,默认是白色。由于我们在这次shader中要调用_MainTex这个自定义属性,所以我们要在Pass中定义它为sampler2D。sampler2D是一个CG中的一中数据类型,主要是用来储存纹理信息的,常见的一般有sampler,sampler2D,samplerCUBE等。我们这里要定义_MainTex所以进行定义

sampler2D _MainTex;
fixed4 _Color;

然后我们正常定义顶点着色器于片元着色器的入口,一般我会直接定义

#pragma vertex vert
#pragma fragmengt frag

我们暂时先不管结构体struct的书写,根据上篇的内容我们应该直接进行顶点着色器的编写,我们顺着这个思路走

float4 vert(float4 vertex:POSITION):SV_POSITION{
    return UnityObjectToClipPos(vertex);
}

这部分跟上篇一样,我们略过翻译,接着我们开始写本篇的新内容,纹理贴图。_MainTex,

fixed4 frag( float2 uv:TEXCOORD0):SV_Target{
    return tex2D(_MainTex,uv);
}

这部分,已经有了区别,首先我们的片元着色器需要输入渲染pipline中三角形遍历后所产生的UV,又因为UV只是一个二维坐标,所以以float2来定义,而TEXCOORD0则是将第一套纹理指定给UV这个参数,让其获得上一步片元坐标参数。之后我们在函数中返回的是一个tex2D的值,tex2D是一个对纹理采样的值,他的第一个参数是需要被采样的值,第二个参数是一个float2的纹理坐标。所以这个过程等于在像素点上进行点对点的涂色。所以按照上一篇的思路,我们的texture shader应该是如下

Shader"Custom/textureShader"{
    Properties{
        _MainTex("Main Tex",2D)="white"{}
        _Color("COlor Tint",Color)=(1,1,1,1)
    }
    Subshader{
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            fixed4 _Color;
            sampler2D _MainTex;

            float4 vert (float4 vertex:POSITION):SV_POSITION{
                return UnityObjectToClipPos(vertex);
            }

            fixed4 frag(float2 uv:TEXCOORD0):SV_Target{
                return tex2D(_MainTex,uv);
            }

            ENDCG
        }

    }
    Fallback "Diffuse"
}

这个shader在unity中没有报错,但呈现的效果不是我们想要的。

我们并不是想要这种纯色的贴图,而是更加丰富的

但这个错误只在5.6之后的版本有,5.6之前的没有,只能说是更新错误的锅,有一种解释是,5.6更新之后一个特性是不能直接在vertex的函数中获取POSITION再返回,必须要从结构体中获取再返回,但我试了试这种说法,依然不行,应该是在vertex与fragment中同时又相同的UV的坐标返回才能有正常的结果,也就是说vertex与fragment中的uv应当匹配才能有正确的结果,所以我们进行结构体的编写,这部分编写与C语言很相似首先是顶点着色器的输入结构体,首先我们要从顶点数据中获得两个数据,一个是我们一定要的顶点坐标,也就是POSITION,另外一个是顶点的UV坐标也就是说基于模型本身参考系的UV坐标进行转换成我们需要的基于摄像机的UV坐标,所以我们第一个结构体可以这么写

struct a2v{
        float4 vertex:POSITION;
        float2 uv:TEXCOORD0;
};

写完了结构体,我们可以在vertex中直接调用这个结构体作为输入

v2f vert(a2v v):SV_POSITION{
    v2f o;
    o.vertex=UnityObjectToClipPos(v.vertex);
    o.uv=v.uv;
    return o;
}

当我们进行带有结构体的顶点着色器的编辑的时候,由于输出的并不一定是同一类型的数据,所以我们不会在函数上进行输出定义,我们输出的更多是一个带有多种属性的参数,一般我们就用o来作为顶点着色器的输出参数。所以这个顶点着色器的输入是参数v,v的数据从渲染pipline的顶点数据取得。v2f o;中的v2f 也是CG中的语句,意思是vertex to fragment 将顶点着色器中的数据传输到片元着色器中,相当于一个参数定义,将输出的参数暂时先储存在o下面。而o.uv=v.uv就是字面意义的坐标传输。

接着我们来定义fragment的结构体,我们从顶点着色器拿到了什么?变换后的顶点坐标与uv坐标,那我们要输出什么,顶点坐标不用变换,但我们需要输出的是一张我们准备好的自定义贴图而不是单纯的uv坐标,所以我们要用我们准备好的_MainTex匹配所得到的uv坐标,通俗的讲就是用贴图,点对点的进行每一个像素的颜色填充。所以我们首先定义结构体

struct v2f{
    float4 pos:SV_POSITION;
    float2 uv:TEXCOORD1;
};

我们将裁剪后的顶点数据给到pos,第二套纹理坐标给到uv作为fragment的输入。之后我们片元着色器的输出依旧是fixed4颜色,但这里我们将用tex2D函数进行对坐标的填充。

fixed4 frag(v2f i):SV_Target{
    return tex2D(_MainTex,i.uv)
}

结合上文,我们的完整代码应该是如下

Shader"Custom/textureShader"{
	Properties{
		_MainTex("Main Tex",2D) = "white"{}
		_Color("COlor Tint",Color) = (1,1,1,1)
	}
		Subshader{
			Pass{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag

				fixed4 _Color;
				sampler2D _MainTex;

				struct a2v {
					float4 vertex:POSITION;
					float4 uv:TEXCOORD0;
				};

				struct v2f {
					float4 pos:SV_POSITION;
					float2 uv:TEXCOORD1;
				};

				v2f vert(a2v v):SV_POSITION {
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					o.uv = v.uv;
					return o;
				}

				fixed4 frag(v2f i) :SV_Target{
					return tex2D(_MainTex,i.uv);
				}

				ENDCG
			}

		}
			Fallback "Diffuse"
}

但别急得激动,因为这个代码会报错,他给的报错信息应该是

invalid output semantic 'SV_POSITION': Legal indices are in [0,0]

错误是在SV_POSITION这个语义上,我们再次理解下这个语义,SV_POSITION是裁剪之后的空间顶点坐标,这个信息我们应该存储在了struct v2f这个结构体的pos之中,而这个结构体已经在frag作为输入中输出了这个顶点间坐标,而v2f vert(a2v v)所输出的也并不是仅仅的空间坐标,在这其实把SV_POSITION删除是一个正确的选项。所以修改之后的代码是

Shader"Custom/textureShader"{
	Properties{
		_MainTex("Main Tex",2D) = "white"{}
		_Color("COlor Tint",Color) = (1,1,1,1)
	}
		Subshader{
			Pass{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag

				fixed4 _Color;
				sampler2D _MainTex;

				struct a2v {
					float4 vertex:POSITION;
					float4 uv:TEXCOORD0;
				};

				struct v2f {
					float4 pos:SV_POSITION;
					float2 uv:TEXCOORD1;
				};

				v2f vert(a2v v){
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					o.uv = v.uv;
					return o;
				}

				fixed4 frag(v2f i) :SV_Target{
					return tex2D(_MainTex,i.uv);
				}

				ENDCG
			}

		}
			Fallback "Diffuse"
}
正确输出的贴图纹理

至此,我们在unity shader的编写结束。

我们接着在shader graph中实现我们的贴图效果

shader graph中的2D纹理贴图

需要注意的是如果我们直接创建texture节点是不能直接拖入Albedo的输入的,在shader graph中需要像上面的shader代码一样,进行一个纹理值的采样才能正常输入albedo。所以我们要将texture节点作为输入连接sampler2D这个节点,然后再将sampler2D这个节点的输出作为albedo的输入。

然后是我们再UE4中的材质shader编写,在UE4的shader编辑中,他的texture sample节点会总动将纹理进行采样,所以我们只需要进行将texture sample的rgb节点连接到basecolor就能将纹理输出

UE4中的纹理贴图

SD中进行纹理贴图与UE4中相似,但贴图节点的名称为bitmap。

SD中的bitmap节点

bitmap主要存储的是一个rgba的像素组,但SD作为一个造轮子的软件,他的材质不需要对geometry负责,所以一般不会在SD中进行使用贴图,也有其他的情况需要进行赋予贴图,这里不再赘述。

Shader入门——1.基本框架

一个仅仅是表现rgb的shader

一个简单的rgb颜色的渲染

Shader "Custom/RgbShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Pass
		{
			CGPROGRAM
 
			#pragma vertex vert
			float4 vert(float4 pos:POSITION):POSITION
			{
			return UnityObjectToClipPos(pos);
			}

			#pragma fragment frag
			fixed4 frag():COLOR
			{
				return fixed4(1,0,0,1);
			}
			ENDCG
		}
    }
    FallBack "Diffuse"
}

逐行翻译一下,

首先Shader”Custom/RgbShader”这个是此shader的名称及再unity 的内部路径,名字叫“RgbShader”存储在Custom下

其次Properties是该shader能在unity右边功能栏能显示的属性,以下划线“_”开头”_Name”能够在此shader中直接定义然后调用,”(…)”中是他的面板名称与属性,比如_Color(“Color Tint”,Color)就可以翻译成_Color这个属性在unity的功能面板中的名字为”Color Tint”,该属性的属性是“Color”这个Color是内置的一个属性代表颜色,一般是fixed4,储存了R、G、B、A这四种属性。但在用的时候可能就用R,G,B这三个。”=”赋值后面的是默认值。

所以shader代码的开头一般为这样

Shader"Custom/ShaderName"
    Properties{
        _Color{"Color",Color}=(1,1,1,1)
        ...
    }

然后就是我们的Subshader。在unity调用一个shader的时候会扫描该shader下的所有subshader然后选择一个能够在该平台下运行的subshader,如果没有的话会调用Fallback之后语义定义的一个内置的shader来达到降级运行的效果。如果不想调用的话,直接添加Off关闭就行了(Fallback Off)。本shader的降级选项是”Diffuse”这个内置shader。所以一个shader必包含至少一个subshader。而subshader中一定包含起码一个Pass(注意这个Pass必须第一个字母大写),因为Pass语句定义了一次完整的渲染过程。在subshader中可以有多个Pass。而有些shader中无Pass,就比如新建的默认standard surface shader,他其中就没有Pass这个语句,因为他在下面的#pragma surface surf Standard fullfowardshadows,进行了一次内置的渲染过程,这个是shaderlab中的默认语句,在编写hlsl时仍需要完整的写出来。所以,添加了这些之后的shader应该是这样的

Shader"Custom/ShaderName"
        Properties{
            _Color("Color",Color)=(1,1,1,1)
        }
        Subshader{
            Pass{
            }
        }
        Fallback "Diffuse"

这样,我们一个基本的shader大框架就搭建完毕了。

然后是CGPROGRAM,这个语句是一个组合,有CGPROGRAM一定有一个ENDCG。这个语句表示这一区域的代码适用于CG语言。当然unity支持opengl与hlsl这两种GPU语言,所以亦可以是HLSLPROGRAM 与 ENDHLSL来组合,这样就告诉编译器,这篇代码是用hlsl来编写的。添加上这部分的代码我们的框架又充实了一点。如下呈现

Shader"Custom/ShaderName"
      Properties{
          _Color("Color",Color)=(1,1,1,1)
          ...
      }
      Subshader{
            Pass{
                 CGPROGRAM
                 ...
                 ENDCG
            }
      }
      Fallback "Diffuse"

如此我们就可以正式的用CG语言编写我们的shader代码了。

#pragma vertex vert

这行代码#pragma是编译指令,告诉编译器我要编译什么函数,在shader中可以编译的一般只有两种着色器,顶点与片元。所以#pragma vertex vert就是告诉编译器我要将vert函数编译成顶点着色器。同理编译片元着色器就是#pragma fragment frag。这样就可以继续丰富我们的代码了。

Shader"Custom/ShaderName"
      Properties{
          _Color("Color",Color)=(1,1,1,1)
          ...
      }
      Subshader{
            Pass{
                 CGPROGRAM
                 #pragma vertex vert
                 #pragma fragment frag
                 ...
                 ENDCG
            }
      }
      Fallback "Diffuse"

然后我们来编写我们的顶点着色器

float4 vert(float4 pos:POSITION):SV_PSOITION{
}

这其中float4其实是一个数组定义,定义了vert这个函数的返回值是一个1X4的数组,每个数的类型是float,()中的float4 pos:POSITION是获取目标的顶点位置作为输入,其中POSITION是一个语义绑定的概念,这个概念相当于告诉unity讲模型的顶点坐标填充到pos这个参数pos中,而SV_POSITION则告诉unity,输出是裁剪空间的顶点坐标,类型就是我们在函数一开始定义的float4。如果没有这些语义的话,unity不知道用户的输入与输出。由于这个着色器是一个很简单的着色器,所以就包含了一行代码。

return UnityObjectToClipPos(pos);

UnityObjectToClipPos()是新版unityshader的内置函数,作用就是相当于原来之前的mul(UNITY_MATRIX_MVP,pos);矩阵变换 。该矩阵变换是将当前的模型的顶点/方向矢量从模型空间变换到裁剪空间。所以完整的顶点着色器的代码如下。(mul是multiple的缩写,在这指乘法)

float4 vert(pos:POSITION):SV_POSITION{
    return UnityObjectToClipPos(pos);
}

同理我们也可以定义片元着色器的函数

fixed4 frag():SV_Target{
    return fixed4 color=(1.0,0.0,0.0,1.0);
}

在这个案例中由于frag函数没有任何输入,输出是一个fixed4的变量,这里就直接输出一个颜色。其中SV_Target也是一个语义绑定。这个属于HLSL中的一个系统语义,相当于告诉渲染器,把用户的输出颜色储存到一个渲染目标(render target)中,这里将默认储存到帧缓存中。

Shader"Custom/RGBShader"
{
        Properties{
            _Color("Color Tint",Color)=(1.0,1.0,1.0,1.0)
        }
        Subshader{
            Pass{
                  CGPROGRAM
                  #pragma vertex vert
                  #pragma fragment frag
                  
                  float4 vert(float4 pos:POSITION):SV_POSITION{
                          return UnityObjectToClipPos(pos);
                  }

                  fixed4 frag():SV_Target{
                          return fixed4 (1.0,0.0,0.0,1.0);
                  }

                  
                  ENDCG
            }
        }
    Fallback "Diffuse"
}

我们如何在shader graph中实现这个代码效果呢,其实很简单

使用pbr的shader graph来进行连线

创建一个”Color”标签,然后将他连接到Albedo就可以得到预期的效果。

连接一个color 节点就行了。

可我们在UE4的shader节点编辑器里面,也一样,但UE4中暂时没找到像shader graph那样的直接的颜色标签,他以另一种形式呈现,”Constant”是一个常量标签,有4种形式,constant 1是一个一维常量值,在颜色部分仅表示黑白,但他又是浮点数的形式,所以还可以表达灰色这种中间色。constant 2是一个二维的向量表达,但在颜色这里仅有R、G属性,constant 3就是我们这里用的R、G、B颜色表达。所以本例中要表现红色的表面,我们可以直接进行一个“1.0,0.0,0.0”的颜色输出。constant4,就是我们上面shader所写的RGBA的一个表达。

UE4中的基础颜色表达

而在substance design(以后简称SD),中他的表达方式更加直接,可以直接添加Uniform Color标签然后添加到Base Color的输出。

SD中的基础颜色表达

到此,我们第一个基础的颜色表达已经完成。