唔,又是好久没写博客了,今天终于算是粗略地过了一遍,这两天就集中把最后几章好好写一写再复习一下。

总之,第十章还是比较重要的。这张里面,我们学习到一些高级纹理的使用和如何得到这些高级纹理,例如,这里的CubeMap和那些渲染纹理我们在之后的章节中,实在反反复复地用到的。

(一)CubeMap

cubemap,立方体纹理,我们在天空盒的实现中就已经遇到过了,这一小节,重点是如何利用摄像机制作CubeMap,以及与之相关的反射和折射该如何实现。

如何使用CubeMap,我就不多说了,实际上就是函数名字变成了texCUBE,本质上还是采样。

这里先贴一个制作CubeMap的脚本

public class RenderCubemapWizard : ScriptableWizard {
	
	public Transform renderFromPosition;
	public Cubemap cubemap;
	
	void OnWizardUpdate () {
		helpString = "Select transform to render from and cubemap to render into";
		isValid = (renderFromPosition != null) && (cubemap != null);
	}
	
	void OnWizardCreate () {
		// create temporary camera for rendering
		GameObject go = new GameObject( "CubemapCamera");
		go.AddComponent<Camera>();
		// place it on the object
		go.transform.position = renderFromPosition.position;
		// render into cubemap		
		go.GetComponent<Camera>().RenderToCubemap(cubemap);
		
		// destroy temporary camera
		DestroyImmediate( go );
	}
	
	[MenuItem("GameObject/Render into Cubemap")]
	static void RenderCubemap () {
		ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
			"Render cubemap", "Render!");
	}
}

这个脚本在Editor下编写的,有时间还是得把Editor录得视频看完,用Editor做一些工具还是很方便的。

准备一个摆好位置的object和一个空的Cubemap即可。

之后让我们着重来讲一下反射和折射,毕竟实际上,物体就是被装在了一个里面贴了图的立方体里,我们这边暂时只考虑,它与天空盒的交互。

在这种环境下做反射,我们能做到很棒的金属效果,对于模型上点反射的颜色,我们可以通过视线方向和顶点法线得到入射光线,再通过入射光线直接采样就好了。

之后将漫反射和上面的反射,用个系数混合一下即可。

下面我就贴个vert和frag的函数好了

之后就是折射了,这里使用的是我们在初中学到的关于折射的公式,仅仅依靠介质折射率之间的比值来实现。

v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.worldViewDir = UnityWorldSpaceViewDir(v.vertex);
                //o.worldLightDir=UnityWorldSpaceLightDir(v.vertex);   // 确实,使用了worldLightDir结果会变得很奇怪

                o.worldRefr = refract(-normalize(o.worldViewDir),normalize( o.worldNormal),_RefractRatio);  //这里为什么是viewdir呢?
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 worldNormal=normalize(i.worldNormal);
                fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
                
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse=_LightColor0.rgb*max(0,dot(worldNormal,worldLightDir));

                fixed3 Refract=texCUBE(_CubeMap,i.worldRefr).rgb*_RefractColor.rgb;

                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
                fixed3 finalColor=ambient+lerp(diffuse,Refract,_RefractAmount)*atten;
                return fixed4(finalColor,1);
            }
            ENDCG
        }

代码里面可能会有我的一些傻问题,有些没解决的,我会在写这个博客的时候去问问。上面的当然应该是用viewdir,不然怎么得到入射光线,估计是我当时没搞懂函数的意思,这里的refract,应当与reflect是差不多的作用,光路可逆得到入射。

下面我们来看大名鼎鼎的——(v)Fresnel反射。基本所有物体都有菲涅耳反射,它用于得到一个系数,来混合我们的反射及漫反射乃至后面的折射。

代码如下

v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.worldViewDir = UnityWorldSpaceViewDir(v.vertex);

                o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);  

                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 worldNormal=normalize(i.worldNormal);
                fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
                
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse=_LightColor0.rgb*max(0,dot(worldNormal,worldLightDir));

                fixed3 reflect=texCUBE(_CubeMap,i.worldRefl).rgb;
                fixed3 Fresnel=_FresnelScale+(1-_FresnelScale)*pow(1-dot(i.worldViewDir,i.worldNormal),5);      //f+(1-f)*(1-(viewDir*normal)^5)

                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
                fixed3 finalColor=ambient+lerp(diffuse,reflect,saturate(Fresnel))*atten;
                return fixed4(finalColor,1);
            }

(二)渲染纹理

(2.1)mirror

渲染纹理这一小节的重要点在于如何得到一些我们想要的画面,把他们作为纹理,我们把它们称作为渲染纹理。

首先从mirror的例子来看,如何在一个房间内出现一个镜子呢?这里的作法我们直接选择将摄像机渲染得到的画面景象呈现在我们之前就好了。

代码重点的就是在顶点着色中,将uv.x左右反转就行了。

v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                //o.uv.x=1-o.uv.x;
                return o;
            }

如此这般,我们就能获得一个简易的镜面效果了,实际工程里还是需要场景和摄像机的位置相互配合一样的,才能得到令人信服的视觉效果。

这里记录下怎么获得到摄像机的画面。如下,在摄像机下,把Target Texture改成我们设置的就好了。

(2.2)玻璃

和之前的折射反射差不多,我们这里接触到了另一种得到渲染纹理的方法。GrabPass{“纹理名”},只需要再定义一个Pass即可。当然可以不用指定纹理名,但这会使性能消耗变大,指定名字后,后续用到该纹理的pass就不用执行一次性能代价昂贵的grab操作了。

这里我们直接贴subPass的代码。玻璃效果是对反射折射效果的一个集合,里面还有对切线空间法线贴图的使用,是一个非常综合的pass.

SubShader{
        Tags{"Queue"="Transparent" "RenderType"="Opaque"}

        GrabPass{"_RefractionTex"}   //这里grab以后就可以直接用了

        Pass{
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

        struct appdata{
            float4 vertex:POSITION;
            float3 normal:NORMAL;
            float4 tangent:TANGENT;
            float4 texcoord:TEXCOORD0;

        };


        struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 TtoW0:TEXCOORD1;
                float4 TtoW1:TEXCOORD2;
                float4 TtoW2:TEXCOORD3;

                float4 pos:SV_POSITION;
                float4 scrPos:TEXCOORD4;
            };


        sampler2D _MainTex;
        float4 _MainTex_ST;
        sampler2D _BumpMap;
        float4 _BumpMap_ST;
        samplerCUBE _CubeMap;
        float _Distortion;
        fixed _RefractAmount;
        sampler2D _RefractionTex;
        float4 _RefractionTex_TexelSize;  //在要对采样坐标进行偏移时,总是需要这个变量

        v2f vert (appdata v)
        {
            v2f o;
            o.pos=UnityObjectToClipPos(v.vertex);
            o.scrPos=ComputeScreenPos(o.pos);

            o.uv.xy=TRANSFORM_TEX(v.texcoord,_MainTex);
            o.uv.zw=TRANSFORM_TEX(v.texcoord,_BumpMap);

            float3 worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
            float3 worldNormal=UnityObjectToWorldNormal(v.normal);
            float3 worldTangent=UnityObjectToWorldDir(v.tangent.xyz);
            float3 worldBinormal=cross(worldNormal,worldTangent)*v.tangent.w;

            o.TtoW0=float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
            o.TtoW1=float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
            o.TtoW2=float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);

            return o;


        }


        fixed4 frag (v2f i) : SV_Target
        {
            float3 worldPos=float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
            float3 worldViewDir=normalize(UnityWorldSpaceViewDir(worldPos));
            float3 bump=UnpackNormal(tex2D(_BumpMap,i.uv.zw));

            //先算refract,_Distortion影响折射强度
            float2 offset = bump.xy*_Distortion*_RefractionTex_TexelSize.xy;
            i.scrPos.xy += offset;
            fixed3 refrCol=tex2D(_RefractionTex,i.scrPos.xy/i.scrPos.w).rgb;

            //再算reflect,这里bump的计算一定要记住
           bump=normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
            float3 reflDir=reflect(-worldViewDir,bump);
            fixed3 texColor=tex2D(_MainTex,i.uv.xy);
            fixed3 reflCol=texCUBE(_CubeMap,reflDir).rgb*texColor.rgb;

            fixed3 finalColor=_RefractAmount*refrCol+(1-_RefractAmount)*reflCol;
            return fixed4(finalColor,1);
        }
        ENDCG
        }
    }

(2.3)总结

使用第一种的渲染纹理性能上肯定是比grabPass好的。

除此之外好像还有图像缓冲的方式,docs.unity3d.com/Manual/GraphicsCommandBuffers.html,网址我先放这了。这种书上没讲的,更要康一下。

(三)程序纹理

这里介绍了一个很简单的脚本来生成一个程序化脚本。

由于太长了,一些属性访问相关的就不写上来了。

private Texture2D _GenerateProceduralTexture()
    {
        Texture2D proceduralTexture = new Texture2D(textureWidth, textureWidth);

        float circleInterval = textureWidth / 4.0f;
        float radius = textureWidth / 10.0f;
        float edgeBlur = 1.0f / blurFactor;

        for (int w = 0; w < textureWidth; w++)
        {
            for (int h = 0; h < textureWidth; h++)
            {
                //使用背景颜色初始化
                Color pixel = backgroundColor;

                //依次画九个圆
                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        Vector2 circleCenter = new Vector2(circleInterval * (i + 1), circleInterval * (j + 1));

                        float dist = Vector2.Distance(new Vector2(w, h), circleCenter) - radius;

                        Color color = _MixColor(circleColor, new Color(pixel.r, pixel.g,
                        pixel.b, 0.0f), Mathf.SmoothStep(0f, 1.0f, dist * edgeBlur));

                        // 与之前得到的颜色进行混合
                        pixel = _MixColor(pixel, color, color.a);

                    }
                }
                proceduralTexture.SetPixel(w, h, pixel);

            }

        }
        proceduralTexture.Apply();
        return proceduralTexture;

    }

重要的就是如何将我们脚本画的图案绘制到纹理上。但感觉单纯这种比较难应用到工程中。

后面这种看上来就比较强大了。

有时间就试试,一些笔刷是否就是调整了里面的参数实现的呢?

您必须 登录 才能发表评论