解读Unity中的CG编写Shader系列三

转自http://www.itnose.net/detail/6096068.html

在上一个例子中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上。这篇文章将要在此基础上研究片段的擦除(discarding fragments)和前面剪裁、后面剪裁(front face culling and back face culling)来达到透明效果。

当一个mesh组件的信息被传递后,我们可以通过代码决定哪些部分渲染(render)出来,而哪些部分不要,这个过程就像把那些不要的部分剔除了,我们看不到他,虽然他的mesh信息还在,但是我们的GPU不会去处理它,肯定比剔除前GPU的性能消耗要低。

这个过程就好比我们的mesh组件是一个透明的膜,我们假设这个胶纸我们根本看不到,而片段着色器在着色的时候像毛笔选择性地上色,最后的效果是我们可能看到膜的一部分是可见的,但是不见的地方,膜还是存在的,只是我们没有给他上色,我们既看不看他们,也不需要再他们上面画宝贵的墨水(GPU并行处理能力)

所以我们可以来改造一下上一个例子中的经度绿色假彩色球体,将其经度>0.5的部分擦掉,那么代码应该相应修改为:

Pass{
		Cull Off // 关掉裁剪模式,作用后面再说
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"
		struct vertexOutput {
			float4 pos : SV_POSITION;
			//由顶点着色器输出mesh信息中的纹理坐标,这个坐标是以对象为坐标系的
			float4 posInObjectCoords : TEXCOORD0;
		};
		vertexOutput vert(appdata_full input)
		{
			vertexOutput output;
			output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
			//直接把texcoord传递给片段着色器
			output.posInObjectCoords = input.texcoord;
			return output;
		}
		float4 frag(vertexOutput input) : COLOR
		{
			//当坐标的y值大于0.5的时候擦除片段
			if (input.posInObjectCoords.y > 0.5)
			{
				discard;
			}

			//其余部分仍然按y值大小生成经度绿色球
			return float4(0.0, input.posInObjectCoords.y , 0.0, 1.0);
		}
		ENDCG
		}

  

那么把这个shader给material,然后给一个球体可以看到我们上次见到的绿色假彩色球只剩下南半球了:

从正面看起来像是实心的

稍微倾斜一下从上面看过去可以看到球体内部是空心的,所以我用膜和毛笔来比喻这个render过程。

我们来把球体换成立方体,看看是什么样子:

可以发现这是一个诡异的立方体,立方体的六个面分别只绘制了一半,且都是下面的一半。

为啥立方体和球体上的效果差别这么大呢?

因为立方体是直角坐标系,球体是极坐标系啊…………扇耳光~~~还给老师了吗 吗吗吗吗吗

同理我们将>0.5改为<0.5,就可以得到球体的北半球。

这是最简单的表面剔除(cuteaway)

更好一点的表面剔除是将片段的位置从对象坐标系转换到世界坐标系,然后根据基础矩阵进行变换可以计算出哪些片段位于其他球体的内部(原始半径是0.5),然后再将位于其他球体内部的表面剔除,这样的话假如两个球互相重叠一部分,那么即使两个球互相绕着自己的球心怎么旋转,没有重叠的部分都会被绘制,而重叠的部分不会被绘制,反正我们看不到,这样省性能。因为即使球体旋转,物体的坐标经过unity的内建矩阵变换为世界坐标后,重叠部分的世界坐标是固定的,所以不会出现两个球体重叠部分表面被裁剪后,旋转一个球之后慢慢看到被裁剪的那个洞了。(因为前面的方法是按对象坐标系裁剪的)

前面与后面剪裁

刚刚的代码中我们看到了Cull Off,这行代码位于CGPROGRAM标记之前,所以他不属于CG的范畴。它是我们Unity中的ShaderLab的指令,所以他不需要分号来结尾。

Cull Off 即为关掉三角形剪裁(为何突然冒出来了三角形,脑补一下,我们的立体图像在计算机中是以三角形拼凑的,正因为如此我们的三维图形才会产生锯齿,那都是三角形的功劳啊)

Cull Front 为前面(外部)剪裁

Cull Back 为后面(内部)剪裁,而这是我们所有Shader的默认模式,也就是说如果Shader不是你自己写的,很可能转动我们的半球的时候,你只看的到前方的曲面而不是半球曲面,不信你可以拖个模型看看

至于为何默认是后面剪裁呢,因为大部分情况下我们的渲染都是对整个三维体的表面进行的,那么既然表面全部被渲染,你就看不到正背对着你的部分,所以默认后面剪裁会节省很多物理性能啊!

不过既然我们将表面进行了擦除,那么我们可以透过被擦除的部分看到背面的内表面,那么我们应该修改这个剪裁模式了,就像一个房子有房顶,我们从正上方看不到房子里面的地板,所以地板应该属于剪裁的范畴。但是如果我们把房顶擦除了(推开房顶),还看不到地板那就有点恐怖了,这种事情就要切换剪裁模式

为了更直观的明白这两种模式,我们修改上面的代码为内部/外部剪裁的双通道(Pass),并且每个Pass中的最后着色不同(红和绿)

要明白一点,Unity中的Shader只会执行一个SubShader,但是会执行所有的Pass

修改后的代码:

Pass{
		Cull front // 外部剪裁,那么这个通道可以理解为是给篮球的内表面上色
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"
		struct vertexOutput {
			float4 pos : SV_POSITION;
			//由顶点着色器输出mesh信息中的纹理坐标,这个坐标是以对象为坐标系的
			float4 posInObjectCoords : TEXCOORD0;
		};
		vertexOutput vert(appdata_full input)
		{
			vertexOutput output;
			output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
			//直接把texcoord传递给片段着色器
			output.posInObjectCoords = input.texcoord;
			return output;
		}
		float4 frag(vertexOutput input) : COLOR
		{
			//当坐标的y值大于0.5的时候擦除片段
			if (input.posInObjectCoords.y > 0.5)
			{
				discard;
			}

			//其余部分仍然按y值大小生成经度绿色球
			return float4(0.0, input.posInObjectCoords.y , 0.0, 1.0);
		}
		ENDCG
		} 

		Pass{
		Cull back //内部剪裁,那么这个通道可以理解为是给篮球的外表面上色
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"
		struct vertexOutput {
			float4 pos : SV_POSITION;
			//由顶点着色器输出mesh信息中的纹理坐标,这个坐标是以对象为坐标系的
			float4 posInObjectCoords : TEXCOORD0;
		};
		vertexOutput vert(appdata_full input)
		{
			vertexOutput output;
			output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
			//直接把texcoord传递给片段着色器
			output.posInObjectCoords = input.texcoord;
			return output;
		}
		float4 frag(vertexOutput input) : COLOR
		{
			//当坐标的y值大于0.5的时候擦除片段
			if (input.posInObjectCoords.y > 0.5)
			{
				discard;
			}

			//其余部分仍然按y值大小生成经度红色球
			return float4(input.posInObjectCoords.y, 0.0 , 0.0, 1.0);
		}
		ENDCG
		}

  我们完成了一个拥有两个Pass的Shader,现在看看球体是什么样子:

从顶部往下看,由于完全垂直看下去我们不知道这个球体的凹进去的还是凸出来的,仿佛还是我们上个例子中的绿色经度球,

我们再从底部网上看:

我们还是不知道这个红黑部分是凹的还是凸的,毕竟这是个半球,垂直半球去看没啥发现

我们再从正面偏上看过去:

可见绿黑部分是凹进去的内表面,红黑部分是凸起的外表面~

至此,我们已经可以随心所欲地控制我们的表面哪些地方可见或者不可见啦!

接下来CG还有更神奇的地方等待我们去发现~

时间: 2024-07-28 17:07:50

解读Unity中的CG编写Shader系列三的相关文章

解读Unity中的CG编写Shader系列一

CG=C for Graphics  用于计算机图形编程的C语言超集 前提知识点: 1.CG代码必须用 CGPROGRAM ... ENDCG括起来 2.顶点着色器与片段着色器的主函数名称可随意,但需要再#pragma vert 与#pragma fragment中声明并且与主函数名完全匹配,shader才会找到入口 3.float4是一种压缩数组,float4 vert与float vert[4]严格意义上讲不同,虽然都是存放4个float,但float4作为向量类型做点乘.内积等处理更快速

解读Unity中的CG编写Shader系列四——unity中的圆角矩形shader

上篇文章中我们掌握了表面剔除和剪裁模式 这篇文章将利用这些知识实现一个简单的,但是又很常用的例子:把一张图片做成圆角矩形 例3:圆角矩形Shader 好吧我承认在做这个例子的时候走了不少弯路,由于本人对矩阵的知识掌握已经悉数还给老师,所以一开始用了一些笨办法计算圆角矩形区域. 我们知道TEXTCOORD0是一个以对象为坐标系的坐标,并且范围在该坐标的第一象限,取值为(0,0)到(1,1) 那么我们把每一张图片都看做一张1X1大小的矩形 我们要在1X1大小的矩形中擦除4个角,应该是这样: 以左上角

解读Unity中的CG编写Shader系列9——镜面反射

讨论完漫反射之后,接下来肯定就是镜面反射了 在开始镜面反射shader的coding之前,要扩充一下前面提到的知识,加深理解镜面反射与漫反射的区别. 引用一下一位前人博文中的一些基础概念,特别是关于冯氏反射模型的: 平行光(directional light) 一种是从特定方向射入并只会照亮面对入射方向的物体,我们称之为平行光(directional light). 环境光(ambient light) 另一种光是来自所有方向并且会照亮所有物体,不管这些物体的朝向如何,我们称之为环境光(ambi

解读Unity中的CG编写Shader系列6——漫反射

如果前面几个系列文章的内容过于冗长缺乏趣味着实见谅,由于时间原因前面的混合部分还没有写完,等以后再补充,现在开始关于反射的内容了. 折射与反射 在物理世界中,光的反射与折射往往是同时存在的,光源由真空或者空气中射入一种材料,光在进入这种材料的同时就发生了折射,折射的程度与各个介质的折射率有关,使光的传播路线偏离原来的路线: 继而如果光在通过不同传播介质的表面时,会像乒乓球一样弹回来,我们人眼能够看到东西,都是因为东西会反射光源,如果一种物质无法反射光,或者没有光源,我们就看不到东西.同样对于不同

解读Unity中的CG编写Shader系列6——不透明度与混合

1.不透明度 当我们要将两个半透的纹理贴图到一个材质球上的时候就遇到混合的问题,由于前面的知识我们已经知道了片段着色器以及后面的环节的主要工作是输出颜色与深度到帧缓存中,所以两个纹理在每个像素上的颜色到底以怎样的形式混合在一起最后输出到帧缓存中是我们现在首要讨论的内容. 1.混合(blending) 上一篇文章中的管道环节中的"逐帧操作"环节中的一项操作就是混合操作,可见混合操作是不可编程的固定功能环节. 在混合操作中,我们将片段着色器中计算出来的颜色称之为 "源颜色&quo

解读Unity中的CG编写Shader系列3——表面剔除与剪裁模式

在上一个样例中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上. 这篇文章将要在此基础上研究片段的擦除(discarding fragments)和前面剪裁.后面剪裁(front face culling and back face culling)来达到透明效果. 当一个mesh组件的信息被传递后,我们能够通过代码决定哪些部分渲染(render)出来.而哪些部分不要.这个过程就像把那些不要的部分剔除了,我们看不到他.尽管他的mesh信息还在.可是我们的G

解读Unity中的CG编写Shader系列七(不透明度与混合)

转自http://www.itnose.net/detail/6098539.html 1.不透明度 当我们要将两个半透的纹理贴图到一个材质球上的时候就遇到混合的问题,由于前面的知识我们已经知道了片段着色器以及后面的环节的主要工作是输出颜色与深度到帧缓存中,所以两个纹理在每个像素上的颜色到底以怎样的形式混合在一起最后输出到帧缓存中是我们现在首要讨论的内容. 1.混合(blending) 上一篇文章中的管道环节中的“逐帧操作”环节中的一项操作就是混合操作,可见混合操作是不可编程的固定功能环节. 在

解读Unity中的CG编写Shader系列十 (光滑的镜面反射(冯氏着色))

前文完成了最基本的镜面反射着色器,单平行光源下的逐顶点着色(per-vertex lighting),又称为古罗着色(Gouraud shading).这篇文章作为后续讨论更光滑的镜面反射方式,逐像素着色(per-pixcel lighting),又称为冯氏着色(Phong shading) 逐像素着色Per-Pixel Lighting (冯氏着色Phong Shading) 别把冯氏着色与冯氏反射模型搞混淆了,前问提到了冯氏反射模型,冯氏反射模型是为使计算机模拟接近真实的物体表面光泽提出的模

解读Unity中的CG编写Shader系列10——光滑的镜面反射(冯氏着色)

前文完成了最基本的镜面反射着色器,单平行光源下的逐顶点着色(per-vertex lighting),又称为古罗着色(Gouraud shading).这篇文章作为后续讨论更光滑的镜面反射方式,逐像素着色(per-pixcel lighting),又称为冯氏着色(Phong shading) 逐像素着色Per-Pixel Lighting (冯氏着色Phong Shading) 别把冯氏着色与冯氏反射模型搞混淆了,前问提到了冯氏反射模型,冯氏反射模型是为使计算机模拟接近真实的物体表面光泽提出的模