unity3d 双面渲染shader的编写

来源:互联网 发布:数据分析主要做什么 编辑:程序博客网 时间:2024/04/29 14:42

Unity内置的Shader,都是单面效果,想必导入Mesh的同学都碰到过这样的痛苦,布料飘起的背面部分看起来是空气,汽车透过车窗看到是路面…各种蛋疼。 
有些文章教导大家 把模型做出厚度来吧,这种做法实在太那个啥了……

其实用改写Shader的方法可以很方便的实现双面材质。 
Unity里有3种Shader方式: 
1.Fixed Function Shaders 
2.Vertex and Fragment Shaders 
3. Surface Shaders 
关于这部分的详细介绍,请参考官方的教程。 
这三种方式里,都可以通过直接在Shader代码头部添加一个Cull off 语句,实现强制双面渲染。 
但是直接用Cull off的方式 有个重大的缺陷,这材质从两面看无论贴图、颜色、反光、照明情况,都是一模一样的,这并不符合大多数实际情况的常识。 
在第1和第2种Shader里,是可以通过在一个渲染子程序里用两个渲染Pass来实现双面不同效果的,这部分网上的资料也很多,写起来也很简单直接。

这里主要讨论的是第三种也是最常用的Surface Shader的双面不同效果的实现。 
Surface Shader是不能写在Pass里的,所以要实现它的双面不同效果就要用其他变通的办法。

首先去Unity官方网站下载一个内置Shader的代码包,链接如下:

http://unity3d.com/download_unity/builtin_shaders.zip

打开后看见一堆.shader文件,可以用任何文本编辑器打开。可以看见系统内建的Shader基本都是Surface方式。 
这里随便打开一个Normal-BumpSpec.Shader 这是普通的高光-凹凸贴图材质 
复制代码

1.        Shader "Bumped Specular" {        2.        Properties {        3.            _Color ("Main Color", Color) = (1,1,1,1)4. _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)        5.            _Shininess ("Shininess", Range (0.03, 1)) = 0.078125        6.            _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}        7.            _BumpMap ("Normalmap", 2D) = "bump" {}        8.        }        9.        SubShader {        10.            Tags { "RenderType"="Opaque" }        11.            LOD 400        12.        13.        CGPROGRAM        14.        #pragma surface surf BlinnPhong        15.        16.        17.        sampler2D _MainTex;        18.        sampler2D _BumpMap;        19.        fixed4 _Color;        20.        half _Shininess;        21.        22.        struct Input {        23.            float2 uv_MainTex;        24.            float2 uv_BumpMap;        25.        };        26.        27.        void surf (Input IN, inout SurfaceOutput o) {        28.            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);        29.            o.Albedo = tex.rgb * _Color.rgb;        30.            o.Gloss = tex.a;        31.            o.Alpha = tex.a * _Color.a;        32.            o.Specular = _Shininess;        33.            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));        34.        }        35.        ENDCG        36.        }        37.        FallBack "Specular"        38.        }

简单解说一下几个关键行: 
第一行Shader “Bumped Specular” 指定了这个shader出现在Unity系统Shader菜单里的名字,如果要修改系统内建Shader的源代码,最好把这个名字改掉,否则和系统内建Shader重名啦。我是这样写的: Shader “Hog’s shaders/BumpSpec_Twoside” ,这个shader会出现在Hog’s shaders组里,系统会自动完成这个加载。 
第二行Properties后面的一组以下划线开头的变量表示了这个渲染器需要设置的参数。对于一个高光-凹凸材质来说,需要材质颜色、反光颜色、反光率、材质贴图和法线贴图,这5个变量就对应这5个东西啦,详细请参考系统手册。 
在第11行LOD 400 后面加上一行:Cull off,这个材质就会自动双面渲染了 Cull off表示双面都渲染,不写默认是Cull back,不渲染背面。你也可以写上Cull front,不渲染正面。 
改完这行 ,把第一行改成你希望的名字,把这个shader文件拷贝到工程的assets目录底下,系统就能自动加载啦。 
效果如图: 

双面是双面了,但是哪有两面是一样亮、一样高光区域的….,全透光的砖墙,这种双面很少会用到吧。

如何做到双面不同效果呢? 
前面说了Surface shader是不能写两个pass渲染不同面的,但其实surface方式可以写多个渲染过程,根本不需要pass的概念,Surface Shader可以这样写: 
Call back 
渲染正面的代码 
Call front 
渲染反面的代码

就可以实现双面不同的控制了。 
根据这个原理,其实我们只要把系统内建shader的源代码复制一份,就能实现另一面不同效果了。以下供参考: 
复制代码

1.        Shader "Hog's shaders/BumpSpec_Twoside" {        2.        Properties {        3.        //正面5个参数        4.            _Color ("Main Color", Color) = (1,1,1,1)        5.            _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)        6.            _Shininess ("Shininess", Range (0.03, 1)) = 0.078125        7.            _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}        8.            _BumpMap ("Normalmap", 2D) = "bump" {}        9.        //反面拷贝 改名 也是5个        10.            _BackColor ("Back Main Color", Color) = (1,1,1,1)        11.            _BackSpecColor ("Back Specular Color", Color) = (0.5, 0.5, 0.5, 1)        12.            _BackShininess ("Back Shininess", Range (0.03, 1)) = 0.078125        13.            _BackMainTex ("Back Base (RGB) Gloss (A)", 2D) = "white" {}        14.            _BackBumpMap ("Back Normalmap", 2D) = "bump" {}        15.        }        16.        SubShader {        17.        Tags { "RenderType"="Opaque" }        18.            LOD 400        19.            Cull back        20.        //开始渲染正面        21.        CGPROGRAM        22.        //表明是surface渲染方式 主渲染程序是surf 光照模型是BLinnPhong        23.        #pragma surface surf BlinnPhong        24.        25.        26.        sampler2D _MainTex;        27.        sampler2D _BumpMap;        28.        fixed4 _Color;        29.        half _Shininess;        30.        31.        struct Input {        32.            float2 uv_MainTex;        33.            float2 uv_BumpMap;        34.        };        35.        36.        void surf (Input IN, inout SurfaceOutput o) {        37.            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);        38.            o.Albedo = tex.rgb * _Color.rgb;        39.            o.Gloss = tex.a;        40.            o.Alpha = tex.a * _Color.a;        41.            o.Specular = _Shininess;        42.            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));}        43.        ENDCG        44.        45.        46.            Cull front        47.        //开始渲染反面 其实和就是拷贝了一份正面渲染的代码 除了变量名要改        48.        CGPROGRAM        49.        #pragma surface surf BlinnPhong        50.        51.        sampler2D _BackMainTex;        52.        sampler2D _BackBumpMap;        53.        fixed4 _BackColor;        54.        half _BackShininess;        55.        56.        struct Input {        57.            float2 uv_BackMainTex;        58.            float2 uv_BackBumpMap;        59.        };        60.        61.        void surf (Input IN, inout SurfaceOutput o) {        62.            fixed4 tex = tex2D(_BackMainTex, IN.uv_BackMainTex);        63.            o.Albedo = tex.rgb * _BackColor.rgb;        64.            o.Gloss = tex.a;        65.            o.Alpha = tex.a * _BackColor.a;        66.            o.Specular = _BackShininess;        67.            o.Normal = UnpackNormal(tex2D(_BackBumpMap, IN.uv_BackBumpMap));}        68.        ENDCG        69.        }        70.        FallBack "Specular"        71.        }

这是个双面可以分别指定的高光-凹凸材质,注意几个要点: 
properties部分只能出现一次,所以这是不能直接拷贝的。因为要为双面指定不同的参数,双面的参数变量名肯定不能一样,这个论坛里都是程序猿,没必要多解释了。我简单的把用于正面的5个参数前面都加上了一个Back用于反面。 
在CG代码内部也要对应的应用相应的参数,反面的渲染代码就用刚才全部加了Back的那5个参数。 
正面代码段用Cull back 开始 反面的代码用Cull front开始 
以下是渲染效果: 

一面是砖墙一面是木板。。蛋疼了没 
这个模式下,双面也完全可以指定不同的材质,基本上你不用学习很多内建Shader和CG语法,通过简单的copy-paste就能组合出无穷的双面材质来了。

再提升一下,其实我们常用的双面效果,除了透明的材质以外,无非是两种: 
一是反面和正面同样纹理,但是不需要高光、反射,只需要一个相对黯淡的被环境光照亮的材质,比如砖墙木盒衣服什么的 
二是反面显示为单身或其他纹理,但也不需要高光、反射,只需要被环境光照亮,比如汽车内部 建筑物内部等等。 
第一种情况,反面可以沿用正面纹理,但是以普通的Diffuse方式着色 
第二种情况,反面不指定或者单独指定纹理,也以普通的Diffuse方式着色 
两种情况,反面的渲染都可以借用系统内建Shader的Diffuse渲染代码来实现,方式一的代码: 
复制代码

1.        Shader "Hog's shaders/BumpSpec_Twoside1" {        2.        Properties {        3.            _Color ("Main Color", Color) = (1,1,1,1)        4.            _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)        5.            _Shininess ("Shininess", Range (0.03, 1)) = 0.078125        6.            _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}        7.            _BumpMap ("Normalmap", 2D) = "bump" {}        8.            _BackColor ("Back Main Color", Color) = (1,1,1,1)        9.        }        10.        SubShader {        11.            Tags { "RenderType"="Opaque" }        12.            LOD 400        13.            Cull back        14.        15.        CGPROGRAM        16.        #pragma surface surf BlinnPhong        17.        18.        19.        sampler2D _MainTex;        20.        sampler2D _BumpMap;        21.        fixed4 _Color;        22.        half _Shininess;        23.        24.        struct Input {        25.            float2 uv_MainTex;        26.            float2 uv_BumpMap;        27.        };        28.        29.        void surf (Input IN, inout SurfaceOutput o) {        30.            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);        31.            o.Albedo = tex.rgb * _Color.rgb;        32.            o.Gloss = tex.a;        33.            o.Alpha = tex.a * _Color.a;        34.            o.Specular = _Shininess;        35.            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));}        36.        ENDCG        37.        38.            Cull front        39.        40.        CGPROGRAM        41.        #pragma surface surf Lambert        42.        43.        sampler2D _MainTex;        44.        fixed4 _BackColor;        45.        46.        struct Input {        47.            float2 uv_MainTex;        48.        };        49.        50.        void surf (Input IN, inout SurfaceOutput o) {        51.            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _BackColor;        52.            o.Albedo = c.rgb;        53.            o.Alpha = c.a;        54.        }        55.        ENDCG        56.        }        57.        FallBack "Specular"        58.        }

反面渲染的运算就直接借用了系统的Diffuse Shader,只不过纹理是沿用正面的纹理,只增加了一个反面的颜色变量用来模拟环境光亮度,与纹理混合实现反面效果。渲染效果如下: 

方式二代码: 
复制代码

1.        Shader "Hog's shaders/BumpSpec_Twoside2" {        2.        Properties {        3.            _Color ("Main Color", Color) = (1,1,1,1)        4.            _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)        5.            _Shininess ("Shininess", Range (0.03, 1)) = 0.078125        6.            _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}        7.            _BumpMap ("Normalmap", 2D) = "bump" {}        8.            _BackColor ("Back Main Color", Color) = (1,1,1,1)        9.            _BackMainTex ("Back Base (RGB) Gloss (A)", 2D) = "white" {}        10.        }        11.        SubShader {        12.            Tags { "RenderType"="Opaque" }        13.            LOD 400        14.            Cull back        15.        16.        CGPROGRAM        17.        #pragma surface surf BlinnPhong        18.        19.        20.        sampler2D _MainTex;        21.        sampler2D _BumpMap;        22.        fixed4 _Color;        23.        half _Shininess;        24.        25.        struct Input {        26.            float2 uv_MainTex;        27.            float2 uv_BumpMap;        28.        };        29.        30.        void surf (Input IN, inout SurfaceOutput o) {        31.            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);        32.            o.Albedo = tex.rgb * _Color.rgb;        33.            o.Gloss = tex.a;        34.            o.Alpha = tex.a * _Color.a;        35.            o.Specular = _Shininess;        36.            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));}        37.        ENDCG        38.        39.            Cull front        40.        41.        CGPROGRAM        42.        #pragma surface surf Lambert        43.        44.        sampler2D _BackMainTex;        45.        fixed4 _BackColor;        46.        47.        struct Input {        48.            float2 uv_BackMainTex;        49.        };        50.        51.        void surf (Input IN, inout SurfaceOutput o) {        52.            fixed4 c = tex2D(_BackMainTex, IN.uv_BackMainTex) * _BackColor;        53.            o.Albedo = c.rgb;        54.            o.Alpha = c.a;        55.        }        56.        ENDCG        57.        }        58.        FallBack "Specular"        59.        }

此方式下反面可以单独指定纹理,不指定就直接显示指定的反面颜色,渲染效果如下 

以上只是介绍一个基本思想,在这个基础上能应该能衍生出无穷的变化。对自定义shader有兴趣的可以参考系统手册和Nvidia的CG教学手册。 
不过千万不要动不动就使用双面材质,因为会增加系统负荷,应该只用在需要的地方。

把以上代码起个名字另存为.shader文件,导入工程assets,就能直接使用。

0 0
原创粉丝点击