1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Unity Shader 入门精要阅读笔记_进阶光照和高级纹理

Unity Shader 入门精要阅读笔记_进阶光照和高级纹理

时间:2022-07-15 04:03:53

相关推荐

Unity Shader 入门精要阅读笔记_进阶光照和高级纹理

文章目录

前言一、更加复杂的光照1.1 Unity的渲染路径1.1.1**前向渲染路径**1.1.2 延迟渲染路径 1.2 Unity的光源类型1.3 Unity的光照衰减1.4 Unity的阴影1.4.1 不透明物体的阴影1.4.2 统一管理光照衰减和阴影 二、高级纹理2.1 立方体纹理2.1.1 环境映射 2.2 渲染纹理

前言

这次是更加复杂的光照

提示:以下是本篇文章正文内容,下面案例可供参考

一、更加复杂的光照

1.1 Unity的渲染路径

渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的

主要有以下

前向渲染路径(Forward Rendering Path)延迟渲染路径 (Deffered Rendering Path)

一般情况下,一个项目只用得到一种渲染路径,但是需要的话,可以在摄像机的渲染路径中重新设置,优先级比项目的高

1.1.1前向渲染路径

一次完整的前向渲染,需要渲染该对象的渲染图元,并且计算两个缓冲区(颜色和深度)的信息

对于每个逐像素的光源,都会执行一次以下流程,假如有多个逐像素光源,该物体就需要执行很多次pass,并且再帧缓冲中加起来得到最终结果

unity中的前向渲染路径有三种处理光照(怎么照亮物体)的方式

逐顶点处理逐像素处理球谐函数处理

一个光源用那种处理模式取决于她的类型和渲染模式

光源类型指光源是平行光还是其他的渲染模式指的是该光源是否重要(Important)

在前向渲染中,Unity会自动给所有的光源排个序,一定数量的光源会按照逐像素,顶多四个会按照逐顶点

判断规则:

环境光和自发光计算只会在Bass中被计算一次对于前向渲染,通常会有一个Base pass和一个Additional Pass,Basspass智慧调用一次,而Additional Pass会被调用多次Additional Pass 会有混合叠加(Blend one one)

1.1.2 延迟渲染路径

前向渲染有个问题。当场景中包含大量光源的时候,前向渲染的性能会激素下降

延迟渲染除了前向渲染的颜色和深度缓冲外,还有G缓冲,G是Geometry的缩写,存储了所关心的表面的其他信息,比如法线、位置、和光照计算的材质属性

延迟渲染主要包含了两个Pass

第一个Pass 不进行任何光照计算,仅仅计算那些片元可见,当发现哪个片元可见,就把他存储到G缓冲区第二个pass,利用G缓冲区的各个片元信息,进行真正的光照计算

1.2 Unity的光源类型

平行光、点光源、聚光灯

(其实还有个面光源)

我们会在Unity Shader中访问他们的5个属性:位置、方向、颜色、强度、衰减

,如果有多个平行光Unity会选择最亮的平行光传递给Base Pass 进行逐像素处理,其他的平行光会在Additional Pass 中计算如果场景中没有平行光,Bass Pass会当成全黑的处理

对于 Additional Pass,她的光照处理和Base Pass的是一样的。由于Additional Pass 处理的光源不仅仅是平行光,在计算光源的五个属性时,颜色和强度仍然可以使用_LightColor0,但是其他三个要根据光源类型。

光源方向的计算:衰减

Unity 使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减(Lookup Table , LUT),我们首先得到光源空间下的坐标,然后使用该坐标对衰减纹理进行采样

1.3 Unity的光照衰减

Unity 使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减。

弊端如下

需要预处理得到采样纹理不直观

纹理

(0,0)表明了与光源重合的点的衰减值,(1,1)点表明了在光源空间中最远位置的点的衰减值。

公式

1.4 Unity的阴影

Shadow Map 他把摄像机放在光源的位置上去看,看不到的地方就是阴影区域

我们使用一个额外的pass来判定shadowmap(本质上是一个深度图,记录了从光源方向看到的最近表面位置),这个Pass 就是LightMode设为ShadowCaster的Pass。当开启光源的阴影效果后,底层渲染引擎会先找到这个Pass,更新光源的阴影映射纹理

屏幕空间的阴影映射技术(Screenspace Shadow Map),SSM原本时延迟渲染中产生阴影的方法

先会调用LightMode为ShadowCaster的Pass 获得SM,和摄像机的深度纹理根据光源的SM和摄像机的深度纹理来获得屏幕空间的阴影图两个做对比,摄像机的深度图记录的表面深度大于转换到阴影映射纹理的深度值,这里就是阴影区域tip:我们需要把表面坐标从模型空间变到屏幕空间下,进行采样

总结

接受阴影:在Shader中对阴影映射纹理进行采样投射阴影:把该物体加入到光源的阴影映射纹理的计算中

1.4.1 不透明物体的阴影

Mesh Renderer Cast Shadows 和 Receive Shadows 来设置。

开启了Cast Shadows ,Unity会把该物体加入光源的阴影映射纹理的计算,这是一个 LightMode为 ShadowCaster的Pass来实现的

对于自定义的shader, LightMode为 ShadowCaster的Pass是定义在Fallback内的,而我们要自定义阴影投射代码。具体流程如下

顶点着色器的输出结构体添加内置宏:SHADOW_COORDS

声明一个用于对阴影纹理采样的坐标,参数为下一个可用的插值寄存器的索引值

在顶点着色器内加入另一个内置宏:TRANSFER_SHADOW

用于计算阴影纹理坐标

在片元着色器中计算阴影值,使用了内置宏SHADOW_ATTENUATION

把阴影值和漫反射以及高光颜色相乘即可

1.4.2 统一管理光照衰减和阴影

光照筛检和阴影对物体最终的渲染的结果本质相同,把光照因子和阴影值通光照结果相乘得到最终的渲染结果

即:UNITY_LIGHT_ATTENUATION

第一个参数atten会被他自己声明,用来存储输出的结果第二个参数是结构体v2f,这个参数会传递给SHADOW_ATTENUATION ,计算阴影值第三个参数是世界空间坐标,采样得到光照衰减

二、高级纹理

2.1 立方体纹理

可以用于天空盒子,和环境映射。

对立方体纹理采样,需要提供一个三维的纹理坐标,这个三维坐标表示了我们再世界坐标下的一个3D方向。

天空盒子不用多说。

2.1.1 环境映射

对于反射主要有两个要点:

在顶点着色器中计算该顶点处的反射方向,计算入射光线的方向

在片元着色器中采样cubemap

对于折射,要注意到斯涅尔定律

首先还是要计算透射入射方向

reflect ratio 指的是不同介质投射比,作为参数输入

然后做采样

对于菲涅尔反射

菲尼尔效应描述了一种现象,当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,繁盛折射或者散射,这个悲反射的光和入射光之间存在一定的比率关系,具体的例子就是远处的水看不到水底,近处的水可以看得到。

以下时两个菲尼尔近似等式

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 = mul(UNITY_MATRIX_MVP, v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);o.worldPos = mul(_Object2World, 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"}

2.2 渲染纹理

GPU允许我们把整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(RTT)。与之相关的时多重渲染目标(MRT),这种技术指的是GPU允许我们把场景同时渲染到多个RTT中,而不需要为每个渲染目标单独渲染完整的场景。

Unity里,专门为RTT定义了一种专门的纹理类型,渲染纹理。

实现镜子效果

设置一个render texture ,用一个摄像机照射想要镜子中出现的画面,然后设置摄像机的target texure 为该render texture。

镜子的实现原理就是使用一个渲染纹理作为输入,然后水平翻转并且直接显示到物体上。

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 fragsampler2D _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 = mul(UNITY_MATRIX_MVP, v.vertex);o.uv = v.texcoord;// Mirror needs to filp xo.uv.x = 1 - o.uv.x;return o;}fixed4 frag(v2f i) : SV_Target {return tex2D(_MainTex, i.uv);}ENDCG}} FallBack Off}

实现玻璃效果

当我们在Shader中定义了一个GrabPass后,Unity会把当前屏幕的画面绘制在一张纹理中

一些先验tip:

结论成立

这个代码有些地方挺难看懂、、

Shader "Unity Shaders Book/Chapter 10/Glass Refraction" {//shader需要的属性Properties {//玻璃的材质纹理、法线纹理、环境纹理、折射的扭曲程度_MainTex ("Main Tex", 2D) = "white" {}_BumpMap ("Normal Map", 2D) = "bump" {}_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}_Distortion ("Distortion", Range(0, 100)) = 10_RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0}SubShader {// We must be transparent, so other objects are drawn before this one.//Transparent可以确保其他所有的不透明物体都已经被渲染到了屏幕上//Opaque在使用着色器替换的时候,该物体可以在被需要时正确渲染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//定义了一个抓取屏幕图像的pass,这个pass定义了一个字符串,该字符串内部的名称决定了抓取到的屏幕图像会被存入哪个纹理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;sampler2D _RefractionTex;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 = mul(UNITY_MATRIX_MVP, 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(_Object2World, v.vertex).xyz; fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; //我们要在片元着色器中把法线方向从切线空间变到世界空间下,对cube采样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 spacefixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));// Compute the offset in tangent space//对屏幕图像进行采样,一个1x1的图片//结合先验知识看float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;// 得到真正的屏幕坐标,透视除法fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;// Convert the normal to world spacebump = 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"}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。