J1N9H3
雾、云、天空和大气的环境光源

天空和大气的渲染

单次散射积分实现

shaders/sky_atmosphere.frag
vec3 CalculateScattering(vec3 rayOrigin, vec3 rayDirection, float maxDistance, 
    vec3 background, vec3 lightDirection) {
    ...
    for (int i = 0; i < PRIMARY_STEPS; ++i) {
        vec3 samplePosition = rayOrigin + rayDirection * rayPosition;
        float height = max(length(samplePosition) - planetRadius, 0.0);
        vec3 localDensity = AtmosphereDensity(height) * stepSize;
        opticalDepth += localDensity;

        vec2 lightHit = RaySphereIntersect(samplePosition, lightDirection, atmosphereRadius);
        float lightStepSize = max(lightHit.y, 0.0) / float(LIGHT_STEPS);
        float lightPosition = lightStepSize * 0.5;
        vec3 lightOpticalDepth = vec3(0.0);

        for (int j = 0; j < LIGHT_STEPS; ++j) {
            vec3 lightSample = samplePosition + lightDirection * lightPosition;
            float lightHeight = max(length(lightSample) - planetRadius, 0.0);
            lightOpticalDepth += AtmosphereDensity(lightHeight) * lightStepSize;
            lightPosition += lightStepSize;
        }

        vec3 combinedDepth = opticalDepth + lightOpticalDepth;
        vec3 attenuation = exp(-rayleighBeta * combinedDepth.x
                               - mieBeta * combinedDepth.y
                               - absorptionBeta * combinedDepth.z);
        totalRayleigh += localDensity.x * attenuation;
        totalMie += localDensity.y * attenuation;
        rayPosition += stepSize;
    }

    vec3 transmittance = exp(-rayleighBeta * opticalDepth.x         
                             - mieBeta * opticalDepth.y             
                             - absorptionBeta * opticalDepth.z);    
    vec3 scatteredLight = (rayleighPhase * rayleighBeta * totalRayleigh     
                         + miePhase * mieBeta * totalMie) * lightIntensity;     
    return scatteredLight + background * transmittance;
}

rayleigh 散射

rayleigh 散射由空气中小分子实现,蓝光比红光更容易被散射。

白天,天空蓝光被更多地散射到观察者方向,使天空呈蓝色:

日出和日落时,太阳光经过更长的大气路径,蓝光被大量散射出去,直射太阳光偏橙红色:

mie 散射

米氏散射通常具有明显的前向散射特征,朝太阳方向观察时会看到更强的亮度和光晕:

臭氧吸收

臭氧层对绿光吸收最强,对红光次之,对蓝光吸收最弱。

日出、日落时,光在大气中的传播路径变长,臭氧使部分区域呈现更明显的蓝紫色或粉紫色过渡:

从太空观察时,大气分子产生的 rayleigh 散射使行星边缘呈现蓝色光环,臭氧吸收进一步削弱部分红光和绿光:

查找表加速实现

使用查找表加速结果如下(单位 毫秒),其中 primary steps 和 light steps 均设置为 64:

0 10 20 30 40 2k 1080p 800x600 Bar 1 Bar 2 Bar 3 2k: 4444 1080p: 2323 800x600: 3.73.7 2k: 2.62.6 1080p: 1.31.3 800x600: 0.30.3 2k: 1.11.1 1080p: 0.50.5 800x600: 0.10.1
  • Bar1:由双重积分暴力实现;
  • Bar2:在 Bar1 的基础上加入 64 light-steps Transmittance LUT;
  • Bar3:在 Bar2 的基础上加上了 Sky-View LUT。

Transmittance LUT

采样 LUT 的主要流程如下:

shaders/sky_atmosphere.frag
vec3 integrateAtmosphereScattering(vec3 rayOrigin, vec3 rayDirection, float maxDistance, vec3 background, vec3 lightDirection) {
	...
	for (int i = 0; i < viewSampleCount; ++i) {
		vec3 samplePosition = rayOrigin + rayDirection * rayPosition;
		float height = max(length(samplePosition) - planetRadius, 0.0);
		vec3 localDensity = AtmosphereDensity(height) * stepSize;
		opticalDepth += localDensity;

		vec2 lightHit = RaySphereIntersect(samplePosition, lightDirection, atmosphereRadius);

		vec3 lightOpticalDepth = vec3(0.0);
		vec3 attenuation = vec3(0.0);

		vec3 viewTransmittance = exp(-rayleighBeta * opticalDepth.x - mieBeta * opticalDepth.y - absorptionBeta * opticalDepth.z);

		if (useTransmittanceLUT) {
			attenuation = viewTransmittance * SampleTransmittanceLUT(samplePosition, lightDirection);
		}
		else { 
			attenuation = viewTransmittance * computeTransmittance(samplePosition, lightDirection, lightSampleCount);
		}

		totalRayleigh += localDensity.x * attenuation;
		totalMie += localDensity.y * attenuation;
		rayPosition += stepSize;
	}
}

计算 LUT 的主要流程如下:

shaders/sky_atmosphere.frag
vec4 computeTransmittanceLUT() {
    int sampleCount = max(transmittanceLUTSteps, 1);
    float viewHeight;
    float viewZenithCos;
    uvToTransmittanceParams(clamp(screenUV, vec2(0.0), vec2(1.0)), viewHeight, viewZenithCos);
    vec3 assumptionPosition = vec3(0, viewHeight, 0);
    vec3 assumptionDirection = zenithCosToDirection(viewZenithCos, 0);
    return vec4(computeTransmittance(assumptionPosition, assumptionDirection, sampleCount), 1.0);
}

Transmittance LUT 记录光线积分部分,后续计算 Sky-View LUT 等直接查询,LUT如下:

天空和大气的渲染

处理行星遮挡

增加地面遮挡判断,保证日落时地球背对太阳的那一侧天空是黑的,否则会有奇怪的漏光。

shaders/sky_atmosphere.frag
for (int i = 0; i < viewSampleCount; ++i) {
    float rayPosition = rayStart + (float(i) + 0.5) * stepSize;
    vec3 samplePosition = rayOrigin + rayDirection * rayPosition;
    float radius = length(samplePosition);
    vec3 localDensity = AtmosphereDensity(max(radius - planetRadius, 0.0)) * stepSize;

    opticalDepth += localDensity;

    float zenithCos = dot(samplePosition / radius, lightDirection);
    vec3 viewTransmittance = exp(-Extinction(opticalDepth));
    vec2 groundHit = RaySphereIntersect(samplePosition, lightDirection, planetRadius);
    vec3 lightTransmittance = vec3(0.0);
    if (groundHit.y <= 0.0) {
        lightTransmittance = useTransmittanceLUT
            ? SampleTransmittanceLUT(radius, zenithCos)
            : transmittance(radius, zenithCos, lightSampleCount);
    }
    vec3 attenuation = viewTransmittance * lightTransmittance;

    totalRayleigh += localDensity.x * attenuation;
    totalMie += localDensity.y * attenuation;
}

效果如下:

Sky-View LUT

采样 LUT 的主要流程如下:

shaders/sky_atmosphere.frag
vec3 integrateAtmosphereScattering(vec3 rayOrigin, vec3 rayDirection, float maxDistance, vec3 background, vec3 lightDirection) {
	vec2 atmosphereHit = RaySphereIntersect(rayOrigin, rayDirection, atmosphereRadius);
	if (atmosphereHit.x > atmosphereHit.y) return background;

	float viewHeight = length(rayOrigin);
	if (MODE == 0 && useSkyViewLUT && viewHeight < atmosphereRadius && maxDistance >= 1e20) {
		vec3 upVector = normalize(rayOrigin);
		float viewZenithCosAngle = dot(rayDirection, upVector);
		
		vec3 sideVector = normalize(cross(upVector, rayDirection));
		vec3 forwardVector = normalize(cross(sideVector, upVector));
		vec2 lightOnPlane = vec2(dot(lightDirection, forwardVector), dot(lightDirection, sideVector));
		lightOnPlane = normalize(lightOnPlane);
		float lightViewCosAngle = lightOnPlane.x;

		vec2 skyViewUV = skyViewLutParamsToUv(viewZenithCosAngle, lightViewCosAngle, viewHeight, false);
		vec3 viewTransmittance = useTransmittanceLUT
			? SampleTransmittanceLUT(viewHeight, viewZenithCosAngle)
			: transmittance(viewHeight, viewZenithCosAngle, max(lightSteps, 1));
		return texture(skyViewLUT, skyViewUV).rgb + background * viewTransmittance;
	}

	...
   	for (int i = 0; i < viewSampleCount; ++i) {
        ...
    }
}

计算 LUT 的主要流程如下:

shaders/sky_atmosphere.frag
vec4 computeSkyViewLUT()
{
    float viewHeight = clamp(
        planetRadius + cameraHeight + cameraPos.y,
        planetRadius + 1.0,
        atmosphereRadius - 1.0);

    float viewZenithCos;
    float lightViewCos;
    uvToSkyViewParams(viewZenithCos, lightViewCos, viewHeight, screenUV);

    float sunZenithCos = clamp(normalize(lightDirection).y, -1.0, 1.0);
    vec3 sunDirection = normalize(vec3(
        sqrt(max(1.0 - sunZenithCos * sunZenithCos, 0.0)),
        0.0,
        sunZenithCos));

    float viewZenithSin = sqrt(max(1.0 - viewZenithCos * viewZenithCos, 0.0));
    vec3 viewDirection = normalize(vec3(
        viewZenithSin * lightViewCos,
        viewZenithSin * sqrt(max(1.0 - lightViewCos * lightViewCos, 0.0)),
        viewZenithCos));

    vec3 atmosphereCamera = vec3(0.0, 0.0, viewHeight);

    vec2 planetHit = RaySphereIntersect(atmosphereCamera, viewDirection, planetRadius);
    float sceneDepth = 1e20;
    if (planetHit.y > 0.0)
        sceneDepth = max(planetHit.x, 0.0);

    vec3 radiance = integrateAtmosphereScattering(atmosphereCamera, viewDirection, sceneDepth, vec3(0.0), sunDirection);

    return vec4(radiance, 1.0);
}

横轴非线性编码视线与太阳的夹角,纵轴以地平线为中心编码视线天顶角,利用大气对称性只覆盖半球,LUT如下:

sky_atmosphere_sky_view_lut

On this page