立方体纹理
立方体纹理是环境映射的一种实现方法。立方体纹理就是将沿着世界空间下观察得到的上下左右前后六个方向的图像存储在立方体的六个面上。它可以实现反射和折射的效果。
可以通过3D方向矢量对立方体纹理进行采样,从立方体中心出发,沿着矢量方向延伸就可以和立方体的六个纹理之一发生相交,交点就是需要采样的纹理。
天空盒子
天空盒子用于模拟背景。它是一个盒子,使用skybox时,整个场景被包围在一个立方体内。立方体的每个面使用立方体纹理映射技术。
- 创建一个材质,并选择skybox/6 sided,并将六张纹理图赋值给材质,将纹理的WrapMode设置为Clamp,防止在接缝处出现不匹配。
- 将材质赋给摄像机,或者在Lighting中将材质赋值给Skybox选项(这个方法会将所有摄像机的skybox设置为同一个).
创建立方体纹理
创建立方体纹理的方法有三种,第一种使用特殊纹理,官方推荐,参照第18章。第二种方法创建一个Cubemap(Create,Legacy,Cubmap),然后将6张纹理拖拽到它的面板中。第三种方法使用程序生成纹理,它可以根据物体在场景中的位置的不同,生成它们各自不同的立方体纹理,通过Unity提供的Camera.RenderToCubemap函数来实现。
第三种方法创建立方体纹理的步骤如下:
- 准备好程序脚本。
- 创建一个空的GameObject,将以这个GameObject的位置信息来渲染立方体纹理。
- 创建一个Cubemap用于存储立方体纹理,并将Readable勾选。
- 使用脚本将以GameObject为中心观察到的世界空间下的6张图渲染到立方体纹理中。
Cubemap的facesize越大,分辨率越大,效果越好,占用的内存也越大。
反射
使用立方体纹理使得物体具有反射效果。如图,就像镀了一层金属。
步骤如下:
- 调整模型的位置,将其与生成立方体纹理的GameObject的位置一样。然后将立方体人纹理赋给模型。
- 给Shader 的 properties添加属性_Cubemap(类型为Cube)
- 在顶点着色器中用reflect函数获得物体的反射方向o.worldRefl = reflect(-o.worldViewDir,o.worldNormal);
- 在片元着色器中利用反射方向对立方体纹理_Cubemap进行采样。fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb * _ReflectColor.rgb;
- 最后将反射颜色和漫反射颜色线性混合并和环境光相加后返回。
折射
使用斯涅耳定律,n1sino1=n2sino2来计算折射方向。
步骤如下:
- 调整模型的位置,并赋给材质。
- 在Shader中声明透射比和立方体纹理
- 在 顶点着色器中计算折射方向,需要将入射光线和表面法线归一化。o.worldRefl = refract(-normalize(o.worldViewDir),normalize(o.worldNormal),_RefractRatio);
- 在片元着色器中利用折射方向对立方体纹理进行采样。fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb * _RefractColor.rgb;
- 混合漫反射颜色和折射颜色并和环境光相加后返回。
菲涅尔反射
菲涅尔反射描述这样一种光学现象,即照射到物体表面的光线,一部分被折射一部分被反射,并且折射的光和反射的光存在比例关系。比如在湖边可以清楚的看到脚边的水底,却看不到远处的水下情景。
菲涅尔反射与视角方向有关,近似等式为:F(v,n)=F0+(1-F0)(1-v*n)^5。F0是反射系数。
步骤如下:
- 调整模型的位置,并赋给材质。
- 在Shader中声明菲涅尔反射系数和立方体纹理。
- 在顶点着色器中计算反射方向。o.worldRefl = reflect(-o.worldViewDir,o.worldNormal);
- 在片元着色器中计算菲涅尔反射。fixed fresnel = _FresnelScale + (1 - _FresnelScale)*pow(1-dot(worldViewDir,worldNormal),5);
- 利用菲涅尔反射混合漫反射和反射颜色。fixed3 color = ambient + (lerp(diffuse,reflection,saturate(fresnel))+specular)*atten;
渲染纹理
渲染纹理就是利用一个摄像机,将其看到的图像渲染到一个中间缓冲中 ,这个中间缓冲就是渲染目标纹理。利用渲染纹理,我们可以实现镜子效果,和玻璃效果。
镜子效果
步骤如下:
- 创建一个材质。
- 创建一个立方体将其作为镜子,并调整其位置和大小,将第一步创建的材质赋值给它。
- 创建一个渲染纹理(create,render texture)
- 创建一个摄像机,调整其位置,裁剪平面,视角等,然后将摄像机看到的图像渲染到渲染纹理(将第三部创建的渲染纹理拖拽到摄像机的target texture上)。
- 在shader的顶点着色器中翻转纹理坐标的水平坐标,对渲染纹理进行采样。
总的来说渲染纹理,就是将摄像机看到的图渲染到一张中间图中,将其作为纹理贴图,然后将纹理贴图渲染到物体表面。
玻璃效果
除了使用一个额外的摄像机获取屏幕图像,还可以在unity shader中定义一个GrabPass,然后unity会将当前屏幕的图像绘制在一张纹理中供我们在后续的pass中访问。GrabPass可以用来模拟透明材质,但是与简单的使用透明混合不同,其可以对物体后面的图像进行更复杂的处理。但是要额外小心渲染队列设置,将其设置为Transparent,保证所有不透明物体先被渲染。
步骤如下:
- 在shader的properties中定义玻璃的材质纹理,法线纹理,立方体纹理等。
- 在SubShader中定义GrabPass。GrabPass {"_RefractionTex"}。unity会将当前屏幕图像绘制在_RefractionTex中,供后续Pass访问。
- 在Pass中定义_RefractionTex和_RefractionTex_TexelSize变量,_RefractionTex_TexelSize是文素大小。
- 在顶点着色器中获取顶点的屏幕坐标,o.scrPos = ComputeGrabScreenPos(o.pos);
在片元着色器中利用法线等信息计算偏移量,然后用偏移量对屏幕图像_RefractionTex进行采样,以此来模拟折射效果。
fixed2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy; i.scrPos.xy = offset + i.scrPos.xy; fixed3 refrCol = tex2D(_RefractionTex,i.scrPos.xy/i.scrPos.w).rgb;
然后计算反射方向,用反射方向对Cubemap进行采样。最后将反射颜色和折射颜色混合返回。
总的来说,这个方法就是,利用反射那一节的 方法来计算物体表面的反射颜色,然后利用GrabPass获取到的屏幕图像,对其采样来模拟折射(采样坐标经过了偏移,这样得到的纹理看上去就像扭曲了一样),得到折射颜色,最后将反射和折射颜色混合。
程序纹理
程序纹理就是利用c#脚本来生成纹理图像。