Unity texCUBE(samplerCube, direction)实现类似环境反射

141 篇文章 35 订阅

测试Surface shader中的环境反射

得有个Cubemap来采样

因为之前想给一些Shader测试环境反射效果。
但又没有相关的Cubemap来做测试。
所以只好去找相关的Cubemap的创建方法。
Unity 实现快照当前场景到Cubemap,在从Cubemap中读取每个面的纹理

使用现成Mobile/Skybox/6 Sided就需要提取Cubemap的纹理来设置每个面的纹理
使用现成Mobile/Skybox/Cubemap就不需要提取了,直接用该Cubemap设置材质的Cubemap

  • 快照到Cubemap:Camera.RenderToCubemap(Cubemap);
  • 从Cubemap中读取Texture2D
    • var colors = Cubemap.GetPixels(CubemapFaces);
    • Texture2D.SetPixels(colors);
    • byte[] bs = Texture2D.EncodeToXXX/PNG/JPG/TGA/EXR();
    • Wirte bs to the File.

快照到Cubemap:Camera.RenderToCubemap(Cubemap);

using UnityEngine;

public class CubemapSnapshot : MonoBehaviour
{
    public Camera cam;
    public Cubemap cubemap;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // camera可以newZ一个临时用的GameObject.AddComponent<Camera>();
            // RenderToCubemap完后,可以将此GameObject destroy掉就可以了
            // 这里我就懒得这么写了
            // 另外,要确保Cubemap的readable是勾上的
            // 部分Unity版本勾上readable就会导致unity崩溃
            // 改用Unity 2019.1.7f1就没崩了,用最新的unity就好
            cam.RenderToCubemap(cubemap);
            Debug.Log($"Cubemap snapshot complete!");
        }
    }
}

从Cubemap中读取Texture2D

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

// 提取Cubemap中的Textures
// 但提取后,法线up,down搞反了(其实是Cubemap类的GetPixels(CubemapFaces) API的BUG)
// 你可以新建Materials,选Mobile/Skybox/6 sided的方式来确认就知道了,我自己试过
// 然后将这个材质设置为Scene的Skybox
// 当然你可以直接用Mobile/Skybox/Cubemap方式就好了,就不用提取6 sided了
public class PickupCubemapTex
{
    [MenuItem("Cubemap/PickupTex")]
    public static void PickupTex()
    {
        var obj = Selection.activeObject;

        var cubemap = obj as Cubemap;
        if (cubemap == null)
        {
            Debug.LogWarning($"Selecting one cubemap plz.");
            return;
        }

        var idx = (int)CubemapFace.PositiveX;
        var count = (int)CubemapFace.NegativeZ + 1;
        List<PickupCubemapTexInfo> texs = new List<PickupCubemapTexInfo>();
        Dictionary<CubemapFace, string> nameMap = new Dictionary<CubemapFace, string>();
        nameMap[CubemapFace.PositiveX] = "Right";
        nameMap[CubemapFace.NegativeX] = "Left";
        nameMap[CubemapFace.PositiveY] = "Upwards";
        nameMap[CubemapFace.NegativeY] = "Downward";
        nameMap[CubemapFace.PositiveZ] = "Forward";
        nameMap[CubemapFace.NegativeZ] = "Backward";

        try
        {
            var title = "Select the directory which stored CubeMap Tex.";
            var filepath = AssetDatabase.GetAssetPath(obj);
            var folder = Directory.GetParent(filepath);
            var foldername = EditorUtility.OpenFolderPanel(title, folder.ToString(), string.Empty);
            if (string.IsNullOrEmpty(foldername) || !Directory.Exists(foldername))
            {
                // noop
            }
            else
            {
                for (; idx < count; idx++)
                {
                    var face = (CubemapFace)idx;
                    var ps = cubemap.GetPixels(face);
                    var newTex = new Texture2D(1024, 1024); // 注意这里的texture2d 的width, height对应cubemap中的face size,但类中没定义,所以这里匹配好你的cubemap来使用就好了
                    newTex.SetPixels(ps);
                    texs.Add(new PickupCubemapTexInfo { tex = newTex, name = nameMap[face] });
                }

                foldername = foldername.Replace("\\", "/");
                var cd = Directory.GetParent(Path.GetFullPath("Assets")).FullName;
                cd = cd.Replace("\\", "/");
                Debug.Log($"cd:{cd}");
                foldername = Path.Combine(foldername, obj.name);
                if (!Directory.Exists(foldername))
                {
                    Directory.CreateDirectory(foldername);
                }
                foldername = foldername.Replace("\\", "/");
                Debug.Log($"append assetsname folder:{foldername}");
                foreach (var item in texs)
                {
                    var fullname = Path.Combine(foldername, item.name + ".tga");
                    fullname = fullname.Replace("\\", "/");
                    fullname = fullname.Replace(cd + "/", "");
                    Debug.Log($"fullname:{fullname}");

                    var bs = item.tex.EncodeToTGA();
                    var fs = File.Open(fullname, FileMode.Create, FileAccess.Write);
                    fs.Write(bs, 0, bs.Length);
                    fs.Close();
                    fs.Dispose();
                }
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
            }
        }
        catch (Exception er)
        {
            Debug.LogError(er);
        }
        finally
        {
            foreach (var item in texs)
            {
                Texture2D.DestroyImmediate(item.tex);
            }
            texs.Clear();
        }
    }
}

public class PickupCubemapTexInfo
{
    public Texture2D tex;
    public string name;
}

使用CubemapSnapshot 快照一个Cubemap

在这里插入图片描述
注意:Inspector中的Face size决定new Texture2D的大小。这里做测试才使用这么大,项目中为达到环境反射效果,设置到适合就行

使用PickupCubemapTex提取纹理

先选中上面快照出来的Cubemap
选择菜单Cubemap->PickupCubemapTex
在这里插入图片描述

我们再新建一个Material,Shader设置为Skybox/6 sided,设置好对应的纹理sided

在这里插入图片描述
我们可以看到右下角,显示的,天地倒转了,而且这里头需要将我们将Pickup出来的up,down设置到skybox/6 sided材质中是,需要将up/down掉转来设置,有可能是Cubemap类的GetPixels(CubemapFaces) API的BUG,导致提取出来的纹理上,下都搞反了,因为用的人少,所以bug没在unity的report有人去提,这里我只是刚好测试用到了一下就发现了。(当然,又或者是我的使用姿势不对。)

最终将该6 sided用到scene的skybox设置中查看,顺便我们也将角色的环境反射shader测试了一下直接反射Cubemap(不是6 sided),效果是正确的,建议使用skybox/cubemap的shader而不是6 sided的

效果如下
在这里插入图片描述

该角色测试用的SurfaceShader

这个是官方的SurfaceShader例子中的代码

Shader "Example/WorldRefl" {
	Properties{
	  _MainTex("Texture", 2D) = "white" {}
	  _Cube("Cubemap", CUBE) = "" {}
	}
		SubShader{
		  Tags { "RenderType" = "Opaque" }
		  CGPROGRAM
		  #pragma surface surf Lambert
		  struct Input {
			  float2 uv_MainTex;
			  float3 worldRefl;
		  };
		  sampler2D _MainTex;
		  samplerCUBE _Cube;
		  void surf(Input IN, inout SurfaceOutput o) {
			  o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb * 0.5;
			  o.Emission = texCUBE(_Cube, IN.worldRefl).rgb;
		  }
		  ENDCG
	}
		Fallback "Diffuse"
}

用vs/ps写

主要看:samplerCUBE _ReflectCube的采样的地方就大概知道如何使用了

//jave.lin 2019.06.17
//simple illumination
Shader "Custom/MyReflection"
{
	Properties{
		[NoScaleOffset] _MainTex("Main Tex", 2D) = "white" {}
		[NoScaleOffset] _ReflectCube("Reflect Cube", Cube) = "white" {}
		_ReflectIntensity("Reflection Intensity", Range(0,1)) = 0.5
		_ReflectBrightness("Reflection Brightness", Range(1,2)) = 1
		_SpecularIntensity("Specular Intensity", Range(0,1)) = 1
		_AmbientIntensity("Ambient Intensity", Range(0,1)) = 1
		_SelfShadowColor("Self Shadow Color", Color) = (0.5,0.5,0.5,0.5)
		[Toggle] _HalfLambert("Half Lambert", Float) = 1
	}
	SubShader{
		Pass{
			Tags {"Queue" = "Opaque" "LightMode"="ForwardBase"} // shadow setup1:"LightMode"="ForwardBase"
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fwdbase 	// shadow setup2:fwdbase
			#include "UnityCG.cginc"
			#include "Lighting.cginc" 		// shadow setup3:#include "Lighting.cginc"
            #include "AutoLight.cginc" 		// shadow setup4:#include "AutoLight.cginc"
			struct a2v{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
			};
			struct v2f{
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                fixed4 tc : TEXCOORD1;
				LIGHTING_COORDS(2,3)		// shadow setup5:LIGHTING_COORDS(n,n+1)
                float3 worldNormal : TEXCOORD4;
                float3 worldPos : TEXCOORD5;
				float3 eyeDir : TEXCOORD6;
				float3 lightDir : TEXCOORD7;
            };
			sampler2D _MainTex;
			samplerCUBE _ReflectCube;
			float _ReflectIntensity;
			float _ReflectBrightness;
			float _SpecularIntensity;
			float _AmbientIntensity;
			fixed4 _SelfShadowColor;
			bool _HalfLambert;
			v2f vert(a2v v){
                v2f o = (v2f)0;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				o.eyeDir = normalize(_WorldSpaceCameraPos.xyz - o.worldPos).xyz;
				o.lightDir = normalize(_WorldSpaceLightPos0.xyz);
				TRANSFER_VERTEX_TO_FRAGMENT(o);	// shadow setup6:TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }
			fixed4 frag(v2f i):SV_TARGET{
				float3 normalDir = normalize(i.worldNormal);
				float3 reflectDir = reflect(-i.eyeDir,normalDir);
				fixed3 specularDir = reflect(-i.lightDir,normalDir);
				
                fixed4 c = tex2D(_MainTex, i.uv); // albedo
				fixed4 r = texCUBE(_ReflectCube, reflectDir); // enviroment cubemap
				
				if(_HalfLambert){
					c.xyz *= (dot(i.lightDir,normalDir) + 1) * 0.5; // diffuse
				}else{
					c.xyz *= dot(i.lightDir,normalDir); // diffuse
				}
				c.xyz += c.xyz * UNITY_LIGHTMODEL_AMBIENT.rgb; // ambient

				float specularDotEye = max(0, dot(specularDir, i.eyeDir));
				c.xyz += (_LightColor0.rgb) * specularDotEye * _SpecularIntensity; // specular
				fixed3 skyDir = reflect(float3(0,-1,0),normalDir);
				c.xyz += unity_AmbientSky.rgb * unity_AmbientSky.a * max(0, dot(skyDir, i.eyeDir)) * _AmbientIntensity; // ambient sky
				fixed3 equatorDir = normalize(float3(_WorldSpaceCameraPos.x, i.worldPos.y, _WorldSpaceCameraPos.z) - i.worldPos).xyz;
				equatorDir = reflect(-equatorDir,normalDir);
				c.xyz += unity_AmbientEquator.rgb * unity_AmbientEquator.a * max(0, dot(equatorDir, i.eyeDir)) * _AmbientIntensity; // ambient equator
				fixed3 groundDir = reflect(float3(0,1,0),normalDir);
				c.xyz += unity_AmbientGround.rgb * unity_AmbientGround.a * max(0, dot(groundDir, i.eyeDir)) * _AmbientIntensity; // ambient ground
				c.xyz = lerp(c.xyz,c.xyz*r.xyz*_ReflectBrightness,_ReflectIntensity); // reflection

				UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos); // shadow setup7:UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos)
				atten = (atten + 1) * 0.5;
				c.xyz *= atten; // lighting and shadow attenuation
                return c;
            }

			ENDCG
		}
	}
	Fallback "Diffuse"
}

运行效果

在这里插入图片描述

Project

SurfaceShaderTest_SampleScene_测试环境反射 提取码: 3sng
unity version : 2019.1.7f1 运行SampleScene

总结

SurfaceShader玩玩就好,不必用于项目中,一般都是类似PBR材质才用,除非你知道SurfaceShader的所有功能刚好都是你需要的,否则还是不要用。
因为SurfaceShader编译出来的vs/ps shader挺多的,功能臃肿,项目中还是自己写vs/ps好。

References

【Unity Shader】 CubeMap(立方体贴图) - 里头还有折射,菲涅尔的概念

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值