UnityShader中级篇——前向渲染中Base Pass 与Additional Pass

本文介绍了一个用于 Unity 引擎的 Shader 示例,实现了前向渲染流程。包括了环境光和主要平行光的基础渲染 Pass 以及其它光源的附加渲染 Pass。详细展示了顶点着色器和片元着色器中的光照模型计算过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'


// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'


Shader "Unity Shaders Book/Chapter 9/ForwardRendering"
{
	Properties
	{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
		//控制高光反射的颜色  
		_Specular("Specular", Color) = (1,1,1,1)
		//控制高光区域的大小  
		_Gloss("Gloss", Range(8.0,256)) = 20
	}
		SubShader
	{
		//BasePass,渲染环境光和最重要的平行光
		Pass
	{
		//指明光照模式为前向渲染模式
		Tags{ "LightMode" = "ForwardBase" }


		CGPROGRAM
#pragma vertex vert  
#pragma fragment frag  
		//确保光照衰减等光照变量可以被正确赋值
#pragma multi_compile_fwdbase


		//包含引用的内置文件  
#include "Lighting.cginc"  
		
		//声明properties中定义的属性  
		fixed4 _Diffuse;
	    fixed4 _Specular;
	    float _Gloss;


	//定义输入与输出的结构体  
	struct a2v
	{
		float4 vertex : POSITION;
		float3 normal : NORMAL;
	};


	struct v2f
	{
		float4 pos : SV_POSITION;
		//存储世界坐标下的法线方向和顶点坐标  
		float3 worldNormal : TEXCOORD0;
		float3 worldPos : TEXCOORD1;
	};


	//在顶点着色器中,计算世界坐标下的法线方向和顶点坐标,并传递给片元着色器  
	v2f vert(a2v v)
	{
		v2f o;
		//转换顶点坐标到裁剪空间  
		o.pos = UnityObjectToClipPos(v.vertex);
		//转换法线坐标到世界空间,直接使用_Object2World转换法线,不能保证转换后法线依然与模型垂直  
		o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
		//转换顶点坐标到世界空间  
		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		return o;
	}


	//在片元着色器中计算光照模型  
	fixed4 frag(v2f i) : SV_Target
	{
	//获取环境光 ,只计算一次,在之后的Additional Pass中不会再计算这个部分
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;


	fixed3 worldNormal = normalize(i.worldNormal);
	fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);


	//计算漫反射光照  
	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));


	//获取视角方向 = 摄像机的世界坐标 - 顶点的世界坐标  
	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
	//计算新矢量h  
	fixed3 halfDir = normalize(viewDir + worldLightDir);
	//计算高光光照  
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal,halfDir)),_Gloss);


	//平行光的衰减值为1
	fixed atten = 1.0;


	return fixed4(ambient +(diffuse + specular) * atten,1.0);


	}
		ENDCG
	}


		//Additional Pass,渲染其他光源
		Pass
	{
		//指明光照模式为前向渲染模式
		Tags{ "LightMode" = "ForwardAdd" }


		//开启混合模式,将计算结果与之前的光照结果进行叠加
		Blend One One


		CGPROGRAM
#pragma vertex vert  
#pragma fragment frag  
		//确保光照衰减等光照变量可以被正确赋值
#pragma multi_compile_fwdadd


		//包含引用的内置文件  
#include "Lighting.cginc"  


		//声明properties中定义的属性  
		fixed4 _Diffuse;
	    fixed4 _Specular;
	    float _Gloss;


	//定义输入与输出的结构体  
	struct a2v
	{
		float4 vertex : POSITION;
		float3 normal : NORMAL;
	};


	struct v2f
	{
		float4 pos : SV_POSITION;
		//存储世界坐标下的法线方向和顶点坐标  
		float3 worldNormal : TEXCOORD0;
		float3 worldPos : TEXCOORD1;
	};


	//在顶点着色器中,计算世界坐标下的法线方向和顶点坐标,并传递给片元着色器  
	v2f vert(a2v v)
	{
		v2f o;
		//转换顶点坐标到裁剪空间  
		o.pos = UnityObjectToClipPos(v.vertex);
		//转换法线坐标到世界空间,直接使用_Object2World转换法线,不能保证转换后法线依然与模型垂直  
		o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
		//转换顶点坐标到世界空间  
		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		return o;
	}


	//在片元着色器中计算光照模型  
	fixed4 frag(v2f i) : SV_Target
	{
	fixed3 worldNormal = normalize(i.worldNormal);


	//计算不同的光源方向
#ifdef USING_DIRECTIONAL_LIGHT
	fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
	fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
	


	//计算漫反射光照  
	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));


	//获取视角方向 = 摄像机的世界坐标 - 顶点的世界坐标  
	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
	//计算新矢量h  
	fixed3 halfDir = normalize(viewDir + worldLightDir);
	//计算高光光照  
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal,halfDir)),_Gloss);


	//处理不同的光源衰减
#ifdef USING_DIRECTIONAL_LIGHT
	fixed atten = 1.0;
#else
	/*float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
	fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;*/
	float distance = length(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
	//线性衰减
	fixed atten = 1.0 / distance;
#endif


	return fixed4((diffuse + specular) * atten,1.0);


	}
		ENDCG
	}
	}


		Fallback"Specular"
}

<think>好的,用户想了解双pass合批的实现方法,用于渲染优化图形编程。首先,我需要回忆一下双pass合批的概念,以及相关的技术细节。根据参考中的引用[3],正向渲染中的物体可能需要两个Shader Pass来处理光照,BasePass处理平行光逐顶点光照,而Additional Pass处理其他光源。这可能涉及到合批技术,即减少Draw Call的次数。 双pass合批可能是指将多个物体的渲染命令合并到一个批次中,每个批次处理两个Pass。这样可以减少CPU到GPU的通信开销,提高渲染效率。例如,在OpenGL中,使用实例化渲染(glDrawArraysInstanced)可以一次绘制多个相同物体,引用[2]提到了实例测试的方法,这可能合批有关。 接下来,我需要整理实现双pass合批的具体步骤。首先,可能需要使用Uniform Buffer Objects(UBO)或Shader Storage Buffer Objects(SSBO)来传递每个实例的数据,如变换矩阵。然后,在顶点着色器中,通过实例ID来索引这些数据,实现不同实例的变换。同时,每个Pass需要处理不同的光照计算,BasePass处理主要光源,Additional Pass处理其他光源,并叠加颜色。 需要注意的技术要点包括:避免材质切换以减少状态更改,合理管理渲染顺序,使用深度测试混合模式确保正确渲染。此外,引用[3]提到正向渲染中的多Pass处理,需要确保每个Pass正确处理光源,避免重复计算。 用户可能还想知道双pass合批的应用场景优化效果,比如在大量重复物体渲染时的性能提升。需要指出适用的场景,如UI元素、植被渲染等,并提醒可能不适用于动态变化频繁的物体。 最后,生成相关问题,帮助用户进一步探索相关技术,如实例化渲染的具体实现、合批优化中的常见问题等。确保回答结构清晰,引用正确,符合用户的需求。</think>### 双 Pass 合批的实现方法 双 Pass 合批(Dual-Pass Batching)是一种通过合并多个物体的渲染流程到两个 PassBase Pass Additional Pass)中,以减少 Draw Call 并优化渲染性能的技术[^3]。其核心思想是**通过共享材质参数合并渲染指令,减少 GPU 状态切换 CPU-GPU 通信开销**。以下是具体实现方法: --- #### **1. 基础实现原理** - **Base Pass**:负责渲染物体的基础属性(如漫反射、法线、粗糙度)第一个逐像素光源(通常是主平行光)。 - **Additional Pass**:用于处理其他逐像素光源的光照计算,每个光源单独叠加到 Base Pass 的结果上。 - **合批条件**:只有使用相同材质 Shader 的物体才能合并到同一个批次中,避免材质切换带来的性能损耗。 --- #### **2. 关键技术步骤** ##### **(1) 数据合并实例化渲染** - **Uniform Buffer Objects (UBO)**:将多个物体的变换矩阵(Model Matrix)合并到统一缓冲区,通过实例化绘制(Instanced Drawing)一次性提交所有数据[^2]。 ```cpp // OpenGL 示例:实例化绘制 glBindBuffer(GL_ARRAY_BUFFER, modelMatrixBuffer); glEnableVertexAttribArray(3); glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0); glVertexAttribDivisor(3, 1); // 每实例更新一次属性 // 绘制 100 个实例 glDrawArraysInstanced(GL_TRIANGLES, 0, vertexCount, 100); ``` ##### **(2) Shader 中的双 Pass 处理** - **Base Pass Shader**:计算基础颜色主光源光照。 ```glsl // 顶点着色器:接收实例化数据 in mat4 instanceModelMatrix; void main() { gl_Position = projectionMatrix * viewMatrix * instanceModelMatrix * position; } ``` - **Additional Pass Shader**:叠加其他光源的贡献,需启用混合模式(如 `GL_ONE` + `GL_ONE`)。 ```glsl // 片段着色器:叠加光源颜色 vec3 additionalLight = calculateLight(lightDirection, lightColor); fragColor.rgb += additionalLight * baseColor.rgb; ``` ##### **(3) 状态管理优化** - **避免冗余切换**:在合批统一设置材质参数(如纹理、Uniform 值)。 - **深度测试策略**:Base Pass 写入深度缓冲区,Additional Pass 仅读取深度值以避免重复计算。 --- #### **3. 性能优化注意事项** - **动态合批静态合批**: - **静态合批**:适用于场景中位置固定的物体(如地形、建筑),预合并顶点数据。 - **动态合批**:适用于少量动态物体,需每帧更新变换矩阵。 - **剔除 LOD**:结合视锥剔除(Frustum Culling)细节层次(LOD),减少不必要的渲染负载。 --- #### **4. 适用场景** - **UI 元素批量渲染**:如相同样式的按钮、文字。 - **重复物体渲染**:植被、人群等大量重复但光照需求简单的物体。 - **多光源场景**:需逐像素计算多个光源时,通过分 Pass 降低复杂度。 --- ### 相关问题 1. **实例化渲染如何减少 Draw Call 数量?**[^2] 2. **多 Pass 渲染中如何避免颜色混合错误?**[^3] 3. **动态合批静态合批的性能差异是什么?** 4. **如何通过 Shader 实现逐光源叠加计算?** --- 通过双 Pass 合批,开发者可以在保证光照质量的同时显著提升渲染性能,尤其适用于移动端或高负载场景[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值