Formalize material-driven panoramic skybox path

This commit is contained in:
2026-04-06 00:39:08 +08:00
parent 8151be0f45
commit 66a6818b89
18 changed files with 860 additions and 82 deletions

View File

@@ -1,6 +1,8 @@
// XC_BUILTIN_SKYBOX_OPENGL_PS
#version 430
layout(binding = 0) uniform sampler2D uSkyboxTexture;
layout(std140, binding = 0) uniform EnvironmentConstants {
vec4 gSkyboxTopColor;
vec4 gSkyboxHorizonColor;
@@ -10,10 +12,45 @@ layout(std140, binding = 0) uniform EnvironmentConstants {
vec4 gCameraForwardAndUnused;
};
layout(std140, binding = 1) uniform MaterialConstants {
vec4 gSkyboxTintAndExposure;
vec4 gSkyboxRotationAndMode;
};
in vec2 vNdc;
layout(location = 0) out vec4 fragColor;
const float XC_PI = 3.14159265358979323846;
const float XC_INV_PI = 0.31830988618379067154;
const float XC_INV_TWO_PI = 0.15915494309189533577;
vec3 EvaluateProceduralSkybox(vec3 viewRay) {
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
}
return color;
}
vec2 ComputePanoramicUv(vec3 viewRay) {
float rotation = gSkyboxRotationAndMode.x;
float sinTheta = sin(rotation);
float cosTheta = cos(rotation);
vec3 rotatedRay = normalize(vec3(
viewRay.x * cosTheta - viewRay.z * sinTheta,
viewRay.y,
viewRay.x * sinTheta + viewRay.z * cosTheta));
float u = fract(atan(rotatedRay.z, rotatedRay.x) * XC_INV_TWO_PI + 0.5);
float v = acos(clamp(rotatedRay.y, -1.0, 1.0)) * XC_INV_PI;
return vec2(u, clamp(v, 0.0, 1.0));
}
void main() {
float tanHalfFov = gCameraRightAndTanHalfFov.w;
float aspect = gCameraUpAndAspect.w;
@@ -23,12 +60,12 @@ void main() {
vNdc.x * aspect * tanHalfFov * gCameraRightAndTanHalfFov.xyz +
vNdc.y * tanHalfFov * gCameraUpAndAspect.xyz);
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
vec3 color = EvaluateProceduralSkybox(viewRay);
if (gSkyboxRotationAndMode.y > 0.5) {
vec2 uv = ComputePanoramicUv(viewRay);
color = texture(uSkyboxTexture, uv).rgb *
gSkyboxTintAndExposure.rgb *
gSkyboxTintAndExposure.w;
}
fragColor = vec4(color, 1.0);

View File

@@ -10,10 +10,48 @@ layout(set = 0, binding = 0, std140) uniform EnvironmentConstants {
vec4 gCameraForwardAndUnused;
};
layout(set = 1, binding = 0, std140) uniform MaterialConstants {
vec4 gSkyboxTintAndExposure;
vec4 gSkyboxRotationAndMode;
};
layout(set = 2, binding = 0) uniform texture2D uSkyboxTexture;
layout(set = 3, binding = 0) uniform sampler uLinearSampler;
layout(location = 0) in vec2 vNdc;
layout(location = 0) out vec4 fragColor;
const float XC_PI = 3.14159265358979323846;
const float XC_INV_PI = 0.31830988618379067154;
const float XC_INV_TWO_PI = 0.15915494309189533577;
vec3 EvaluateProceduralSkybox(vec3 viewRay) {
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
}
return color;
}
vec2 ComputePanoramicUv(vec3 viewRay) {
float rotation = gSkyboxRotationAndMode.x;
float sinTheta = sin(rotation);
float cosTheta = cos(rotation);
vec3 rotatedRay = normalize(vec3(
viewRay.x * cosTheta - viewRay.z * sinTheta,
viewRay.y,
viewRay.x * sinTheta + viewRay.z * cosTheta));
float u = fract(atan(rotatedRay.z, rotatedRay.x) * XC_INV_TWO_PI + 0.5);
float v = acos(clamp(rotatedRay.y, -1.0, 1.0)) * XC_INV_PI;
return vec2(u, clamp(v, 0.0, 1.0));
}
void main() {
float tanHalfFov = gCameraRightAndTanHalfFov.w;
float aspect = gCameraUpAndAspect.w;
@@ -23,12 +61,12 @@ void main() {
vNdc.x * aspect * tanHalfFov * gCameraRightAndTanHalfFov.xyz +
vNdc.y * tanHalfFov * gCameraUpAndAspect.xyz);
float vertical = clamp(viewRay.y, -1.0, 1.0);
vec3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0) {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(clamp(vertical, 0.0, 1.0), 0.65));
} else {
color = mix(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(clamp(-vertical, 0.0, 1.0), 0.55));
vec3 color = EvaluateProceduralSkybox(viewRay);
if (gSkyboxRotationAndMode.y > 0.5) {
vec2 uv = ComputePanoramicUv(viewRay);
color = texture(sampler2D(uSkyboxTexture, uLinearSampler), uv).rgb *
gSkyboxTintAndExposure.rgb *
gSkyboxTintAndExposure.w;
}
fragColor = vec4(color, 1.0);

View File

@@ -1,4 +1,7 @@
// XC_BUILTIN_SKYBOX_D3D12_PS
Texture2D gSkyboxTexture : register(t0);
SamplerState gLinearSampler : register(s0);
cbuffer EnvironmentConstants : register(b0) {
float4 gSkyboxTopColor;
float4 gSkyboxHorizonColor;
@@ -8,11 +11,46 @@ cbuffer EnvironmentConstants : register(b0) {
float4 gCameraForwardAndUnused;
}
cbuffer MaterialConstants : register(b1) {
float4 gSkyboxTintAndExposure;
float4 gSkyboxRotationAndMode;
}
struct PSInput {
float4 position : SV_POSITION;
float2 ndc : TEXCOORD0;
};
static const float XC_PI = 3.14159265358979323846f;
static const float XC_INV_PI = 0.31830988618379067154f;
static const float XC_INV_TWO_PI = 0.15915494309189533577f;
float3 EvaluateProceduralSkybox(float3 viewRay) {
const float vertical = clamp(viewRay.y, -1.0f, 1.0f);
float3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0f) {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(saturate(vertical), 0.65f));
} else {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(saturate(-vertical), 0.55f));
}
return color;
}
float2 ComputePanoramicUv(float3 viewRay) {
const float rotation = gSkyboxRotationAndMode.x;
const float sinTheta = sin(rotation);
const float cosTheta = cos(rotation);
const float3 rotatedRay = normalize(float3(
viewRay.x * cosTheta - viewRay.z * sinTheta,
viewRay.y,
viewRay.x * sinTheta + viewRay.z * cosTheta));
const float u = frac(atan2(rotatedRay.z, rotatedRay.x) * XC_INV_TWO_PI + 0.5f);
const float v = acos(clamp(rotatedRay.y, -1.0f, 1.0f)) * XC_INV_PI;
return float2(u, saturate(v));
}
float4 MainPS(PSInput input) : SV_Target {
const float tanHalfFov = gCameraRightAndTanHalfFov.w;
const float aspect = gCameraUpAndAspect.w;
@@ -22,12 +60,12 @@ float4 MainPS(PSInput input) : SV_Target {
input.ndc.x * aspect * tanHalfFov * gCameraRightAndTanHalfFov.xyz +
input.ndc.y * tanHalfFov * gCameraUpAndAspect.xyz);
const float vertical = clamp(viewRay.y, -1.0f, 1.0f);
float3 color = gSkyboxHorizonColor.rgb;
if (vertical >= 0.0f) {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxTopColor.rgb, pow(saturate(vertical), 0.65f));
} else {
color = lerp(gSkyboxHorizonColor.rgb, gSkyboxBottomColor.rgb, pow(saturate(-vertical), 0.55f));
float3 color = EvaluateProceduralSkybox(viewRay);
if (gSkyboxRotationAndMode.y > 0.5f) {
const float2 uv = ComputePanoramicUv(viewRay);
color = gSkyboxTexture.Sample(gLinearSampler, uv).rgb *
gSkyboxTintAndExposure.rgb *
gSkyboxTintAndExposure.w;
}
return float4(color, 1.0f);

View File

@@ -1,5 +1,12 @@
Shader "Builtin Skybox"
{
Properties
{
_Tint ("Tint", Color) = (1,1,1,1) [Semantic(Tint)]
_Exposure ("Exposure", Float) = 1.0 [Semantic(Exposure)]
_Rotation ("Rotation", Float) = 0.0 [Semantic(Rotation)]
_MainTex ("Panoramic", 2D) = "white" [Semantic(SkyboxTexture)]
}
SubShader
{
Pass
@@ -9,6 +16,9 @@ Shader "Builtin Skybox"
Resources
{
EnvironmentConstants (ConstantBuffer, 0, 0) [Semantic(Environment)]
MaterialConstants (ConstantBuffer, 1, 0) [Semantic(Material)]
SkyboxTexture (Texture2D, 2, 0) [Semantic(SkyboxTexture)]
LinearClampSampler (Sampler, 3, 0) [Semantic(LinearClampSampler)]
}
HLSLPROGRAM
#pragma vertex MainVS