如何为3D场景创建带地平线延伸纹理的无限平面背景?
嘿,我之前做户外场景的时候刚好折腾过这个需求,给你分享几个能实现「无限地平线平面+天空盒衔接」的实用方案:
方案1:纹理渐变+透明过渡实现无限平面
这个方案适合你已经有现成地平线纹理的情况,核心是让平面在远处逐渐透明,自然过渡到后方的天空盒:
- 第一步:准备适配的纹理
用图像处理工具(比如PS)给你的地面纹理做渐变处理:底部保留完整的地面细节,往上逐渐过渡到和天空盒地平线一致的颜色,同时给纹理添加alpha通道,让上半部分透明度逐渐变为0。这样平面远处会慢慢透出天空盒。 - 第二步:创建平面并配置Shader
不要做真的“无限大”平面(会拖垮性能),创建一个足够覆盖相机视野的大平面就行。然后给它写个自定义Shader,核心逻辑是根据距离相机的远近调整透明度:// Unity Shader 简化示例 Shader "Custom/HorizonPlane" { Properties { _MainTex ("Ground Texture", 2D) = "white" {} _FadeDistance ("Fade Start Distance", Float) = 500 _MaxFadeDistance ("Full Fade Distance", Float) = 1000 } SubShader { Tags {"Queue"="Transparent" "RenderType"="Transparent"} Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 worldPos : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; float _FadeDistance; float _MaxFadeDistance; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { // 计算相机到平面顶点的世界空间距离 float dist = length(_WorldSpaceCameraPos - i.worldPos); // 计算透明度衰减:距离超过_FadeDistance后开始透明,到_MaxFadeDistance完全透明 float alpha = 1 - saturate((dist - _FadeDistance) / (_MaxFadeDistance - _FadeDistance)); // 采样纹理 fixed4 col = tex2D(_MainTex, i.uv); col.a = alpha; return col; } ENDCG } } } - 第三步:对齐天空盒与平面
调整天空盒的地平线高度,让它和平面纹理的地平线位置视觉上对齐,这样过渡会更自然,不会出现明显的断层。
方案2:程序化混合天空盒与地面(无现成纹理也能用)
如果没有合适的地平线纹理,可以用Shader直接在平面上程序化生成地面效果,并在远处和天空盒无缝融合:
- 核心思路是根据距离相机的远近,在地面纹理/颜色和天空盒颜色之间做插值:
// Unity Shader 简化示例 Shader "Custom/ProceduralHorizonPlane" { Properties { _GroundColor ("Ground Color", Color) = (0.3, 0.25, 0.15, 1) _GroundTex ("Ground Texture", 2D) = "white" {} _TexScale ("Texture Scale", Float) = 10 _BlendStart ("Blend Start Distance", Float) = 400 _BlendEnd ("Blend End Distance", Float) = 800 } SubShader { Tags {"Queue"="Geometry" "RenderType"="Opaque"} Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 worldPos : TEXCOORD1; float3 worldDir : TEXCOORD2; }; sampler2D _GroundTex; float4 _GroundTex_ST; float4 _GroundColor; float _TexScale; float _BlendStart; float _BlendEnd; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _GroundTex) * _TexScale; o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldDir = normalize(o.worldPos - _WorldSpaceCameraPos); return o; } fixed4 frag (v2f i) : SV_Target { float dist = length(_WorldSpaceCameraPos - i.worldPos); // 采样地面纹理并混合底色 fixed4 groundCol = tex2D(_GroundTex, i.uv) * _GroundColor; // 采样天空盒颜色 fixed4 skyCol = UNITY_SAMPLE_TEXCUBE(unity_Skybox, i.worldDir); // 计算混合系数 float blend = saturate((dist - _BlendStart) / (_BlendEnd - _BlendStart)); // 混合地面与天空颜色 return lerp(groundCol, skyCol, blend); } ENDCG } } } - 这种方式不需要额外处理纹理,平面在远处会直接过渡到天空盒的颜色,视觉上完全是无限延伸的效果。
关键注意事项
- 不要创建过大的平面,只要能覆盖相机的最大视野范围就够了,避免不必要的性能开销。
- 调整渲染队列:如果用透明过渡的方案,把平面的渲染队列设为
Transparent,确保天空盒在平面下方渲染;如果是程序化混合的不透明方案,设为Geometry即可。 - 不同引擎的实现逻辑一致:比如Unreal Engine里,你可以在材质蓝图里用
Distance to Camera节点来控制透明度或颜色混合,配合SkySphere使用。
内容的提问来源于stack exchange,提问作者Daarwin




