Cubemap是环境映射的一种实现方法,使用三维纹理坐标进行采样,这个三维纹理坐标代表世界空间下的一个3D方向,这个方向矢量从立方体中心出发,采样的结果由交点计算得出。
缺点:1.当场景引入新的物体、光源或物体发生移动时,都要重新计算;2.仅可以反射环境,不可以反射使用了Cubemap的物体自身,因为Cubemap不能模拟多次反射,如两个金属球互相反射(全局光照可以实现),因此尽量对凸面体使用,不要对凹面体使用,因为凹面体会反射自身。
天空盒
1.新建材质;
2.在材质的unity shader下选择skybox/6 Sided;
3.把6张纹理的wrap mode设置为Clamp,防止接缝处出现不匹配的现象;
4.把材质赋给lighting中的skybox;
5.将camera中的clear flags设置为skybox;
6.如果希望某些摄像机使用不同的天空盒,可以在摄像机上单击component——rendering——skybox来完成覆盖;
创建立方体纹理
方法有三种:
1.准备一张HDR的立方体展开图,纹理类型设置cubemap;(推荐)
2.准备六张纹理贴到创建的Cubemap上;
3.脚本创建;
脚本创建通过Camera.RenderToCubemap实现:
using UnityEngine;
using UnityEditor;
using System.Collections;
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!");
}
}
该脚本将摄像机放到物体位置观察到的六张图像渲染到cubemap中,使用方法:创建gameobject,再创建cubemap(legacy->cubemap),勾选readable,最后打开Render into Cubemap功能渲染即可得到立方体纹理,face size代表渲染质量。
环境映射
反射
把前面得到的cubemap_0放到Reflection Cubemap属性中
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 10/Reflection" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
//控制反射颜色
_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1)
//控制反射程度
_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _ReflectColor;
fixed _ReflectAmount;
samplerCUBE _Cubemap;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
// Compute the reflect dir in world space
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
// Use the reflect dir in world space to access the cubemap
//没有归一化是因为这里的参数只需要是方向变量
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
// Mix the diffuse color with the reflected color
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
折射
斯涅尔定律:
η
1
s
i
n
Θ
1
=
η
2
s
i
n
Θ
2
\eta _{1}sin\Theta _{1}=\eta _{2}sin\Theta _{2}
η1sinΘ1=η2sinΘ2
Θ
\Theta
Θ是光线和法线夹角,折射率:真空为1,玻璃一般为1.5;
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 10/Refraction" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_RefractColor ("Refraction Color", Color) = (1, 1, 1, 1)
_RefractAmount ("Refraction Amount", Range(0, 1)) = 1
//使用该属性来计算不同介质的透射比,来计算折射方向,即入射介质的折射率和折射介质的折射率的比值
_RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5
_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed4 _RefractColor;
float _RefractAmount;
fixed _RefractRatio;
samplerCUBE _Cubemap;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefr : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
// Compute the refract dir in world space
//这个函数需要使用归一化的向量
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
// Use the refract dir in world space to access the cubemap
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
// Mix the diffuse color with the refract color
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
菲涅尔反射
菲涅尔反射是指一部分发生反射,一部分发生折射;被反射的光和入射光之间存在一定比率关系;如:近处的水面可以看到湖底,远处只能看到水面反射。
Schlick菲涅耳近似等式:
F
S
c
h
l
i
c
k
(
υ
,
n
)
=
F
0
+
(
1
−
F
0
)
(
1
−
υ
⋅
n
)
5
F_{Schlick}(\upsilon,n)=F_{0}+(1-F_{0})(1-\upsilon\cdot n )^{5}
FSchlick(υ,n)=F0+(1−F0)(1−υ⋅n)5
F
0
F_{0}
F0是反射系数,控制菲涅耳反射强度。使用近似等式可以在边界处模拟反射光强和折射光强/漫反射光强之间的变化,常用的有车漆、水面渲染。
_FresnelScale为0时边缘发光,为1时完全反射Cubemap中的图像;
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 10/Fresnel" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_FresnelScale ("Fresnel Scale", Range(0, 1)) = 0.5
_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
fixed _FresnelScale;
samplerCUBE _Cubemap;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldViewDir : TEXCOORD2;
fixed3 worldRefl : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 worldViewDir = normalize(i.worldViewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
渲染纹理(RenderTexture)
现代GPU允许将整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(Render Target Texture,RTT),渲染纹理(RT)就是Unity为RTT定义的一种纹理类型。
使用渲染纹理的两种方式:
1.在Project中创建一个渲染纹理,将摄像机的渲染目标设置为该RT;
2.在屏幕后处理时使用GrabPass或OnRenderImage函数来获取当前屏幕图像,Unity会把这个屏幕图像放到一张和屏幕分辨率等同的一张渲染纹理中;
镜子效果
在Project中创建一个渲染纹理,创建一个摄像机,将摄像机的渲染目标设置为该RT;
再把该RT拖到材质中的maintex中;
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 10/Mirror" {
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
struct a2v {
float4 vertex : POSITION;
float3 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
// Mirror needs to filp x
o.uv.x = 1 - o.uv.x;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
FallBack Off
}
玻璃效果
使用GrabPass注意要将渲染队列设置为Transparent;保证先绘制不透明物体。
方法:1.使用一张法线纹理修改模型的法线信息;2.使用Cubemap模拟反射;3.使用GrabPass获取玻璃后面的屏幕图像,并使用切线空间下的法线对屏幕纹理坐标偏移后,再对屏幕图像进行采样来模拟近似的折射效果。
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 10/Glass Refraction" {
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
//玻璃的法线纹理
_BumpMap ("Normal Map", 2D) = "bump" {}
//模拟反射的环境纹理
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
//控制模拟折射时图像的扭曲程度
_Distortion ("Distortion", Range(0, 100)) = 10
//控制折射程度。0为只有反射,1为只有折射
_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0
}
SubShader {
// We must be transparent, so other objects are drawn before this one.
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
// This pass grabs the screen behind the object into a texture.
// We can access the result in the next pass as _RefractionTex
GrabPass { "_RefractionTex" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractAmount;
//使用GrabPass时指定的纹理名称,可以不用名称,但使用名称时更省性能
sampler2D _RefractionTex;
//得到该纹理的纹素大小,如纹理大小为256*512,纹素大小为(1/256,1/512),在对屏幕图像的采样坐标偏移时使用该变量
float4 _RefractionTex_TexelSize;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord: TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};
v2f vert (a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//得到对应呗抓取的屏幕图像的采样坐标
o.scrPos = ComputeGrabScreenPos(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;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 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);
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// Get the normal in tangent space
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
// Compute the offset in tangent space
//使用切线空间的法线方向偏移是因为该空间下的法线可以反应顶点局部空间下的法线方向
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
//使用透视除法得到真正的屏幕坐标
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
//使用该坐标对_RefractionTex采样,得到模拟的折射颜色
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
// Convert the normal to world space
//把法线变换到世界空间,得到视角下相对于法线的反射方向
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 reflDir = reflect(-worldViewDir, bump);
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
//和主纹理颜色相乘得到反射颜色
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;
//混合反射和折射颜色
fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
return fixed4(finalColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
把书中提到的diffuse纹理和normal纹理分别给main tex和normal map属性,再把使用render to cubemap创建的材质赋给environment cubemap;
GrabPass支持两种形式:
1.直接使用GrabPass{},在后续的Pass中直接使用——GrabTexture来访问屏幕图像,但是当有多个物体使用这样的方法会消耗性能,这样可以让每个物体得到不同的屏幕图像。
2.使用GraPass{“TextureName”},这样unity只会在每一帧第一个使用这个名字的纹理的物体抓取屏幕,这个纹理也可以被其他Pass访问,更高效,但是所有物体都会使用同一张屏幕图像;
渲染纹理和GrabPass的不同
在移动设备上,渲染纹理更有效率,因为在高分辨率设备上,GrabPass抓取的图像分辨率和显示屏幕一致,会造成严重的带宽影响,而且它需要直接读取后备缓冲中的数据,破坏了CPU和GPU之间的并行性,比较耗时;
程序纹理
程序纹理是指用计算机生成的图像,通常使用一些特定的算法来创建个性化图案或者非常真实的自然元素,例如木头、石子等。使用程序纹理的好处是我们可以使用各种参数来控制纹理的外观。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[ExecuteInEditMode]
public class ProceduralTextureGeneration : MonoBehaviour {
public Material material = null;
#region Material properties
//SetProperty开源插件,当修改了材质属性时,可以使用_UpdateMaterial函数使用新的属性重新生成程序纹理
[SerializeField, SetProperty("textureWidth")]
private int m_textureWidth = 512;
public int textureWidth {
get {
return m_textureWidth;
}
set {
m_textureWidth = value;
_UpdateMaterial();
}
}
[SerializeField, SetProperty("backgroundColor")]
private Color m_backgroundColor = Color.white;
public Color backgroundColor {
get {
return m_backgroundColor;
}
set {
m_backgroundColor = value;
_UpdateMaterial();
}
}
[SerializeField, SetProperty("circleColor")]
private Color m_circleColor = Color.yellow;
public Color circleColor {
get {
return m_circleColor;
}
set {
m_circleColor = value;
_UpdateMaterial();
}
}
//模糊因子,模糊圆形边界
[SerializeField, SetProperty("blurFactor")]
private float m_blurFactor = 2.0f;
public float blurFactor {
get {
return m_blurFactor;
}
set {
m_blurFactor = value;
_UpdateMaterial();
}
}
#endregion
//变量用来保护生成的程序纹理
private Texture2D m_generatedTexture = null;
// Use this for initialization
void Start () {
if (material == null) {
Renderer renderer = gameObject.GetComponent<Renderer>();
if (renderer == null) {
Debug.LogWarning("Cannot find a renderer.");
return;
}
//从脚本所在物体上获得对应材质
material = renderer.sharedMaterial;
}
//生成程序纹理
_UpdateMaterial();
}
private void _UpdateMaterial() {
if (material != null) {
m_generatedTexture = _GenerateProceduralTexture();
//把纹理赋给材质,材质中需要有叫_MainTex的纹理属性
material.SetTexture("_MainTex", m_generatedTexture);
}
}
private Color _MixColor(Color color0, Color color1, float mixFactor) {
Color mixColor = Color.white;
mixColor.r = Mathf.Lerp(color0.r, color1.r, mixFactor);
mixColor.g = Mathf.Lerp(color0.g, color1.g, mixFactor);
mixColor.b = Mathf.Lerp(color0.b, color1.b, mixFactor);
mixColor.a = Mathf.Lerp(color0.a, color1.a, mixFactor);
return mixColor;
}
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;
// 绘制9个圆
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;
}
}
程序材质
程序材质和程序纹理是使用软件Substance Designer生成,材质通常以.substar为后缀,和平时的材质一样使用,通过调整程序纹理的属性可以得到完全不同的外观,是一种非常强大的材质类型。