Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(atmosphere): Hillarie Phisically Based Atmosphere Shader #444

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions packages/atmosphere/component/AtmosphericComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { AtmosphericScatteringSky, AtmosphericScatteringSkySetting } from "../textures/AtmosphericScatteringSky";
import { SkyRenderer } from "../renderer/SkyRenderer";
import { ShaderLib, Transform } from "@orillusion/core";
import { AtmosphericScatteringSky_shader } from "../shader/AtmosphericScatteringSky_shader";

class HistoryData {
public rotateX: number;
public rotateY: number;

public sunX: number;
public sunY: number;

constructor() {
this.reset();
}

public reset(): this {
this.rotateX = this.rotateY = this.sunX = this.sunY = Number.MAX_VALUE;
return this;
}

public isRotateChange(rx: number, ry: number): boolean {
return Math.abs(this.rotateX - rx) >= 0.001 || Math.abs(this.rotateY - ry) >= 0.001;
}

public isSkyChange(x: number, y: number): boolean {
return Math.abs(this.sunX - x) >= 0.001 || Math.abs(this.sunY - y) >= 0.001;
}

public save(x: number, y: number, rx: number, ry: number): this {
this.sunX = x;
this.sunY = y;
this.rotateX = rx;
this.rotateY = ry;

return this;
}
}

/**
*
* Atmospheric Sky Box Component
* @group Components
*/
export class AtmosphericComponent extends SkyRenderer {

private _atmosphericScatteringSky: AtmosphericScatteringSky;
private _onChange: boolean = true;
private _relatedTransform: Transform;
private _historyData: HistoryData;
public get sunX() {
return this._atmosphericScatteringSky.setting.sunX;
}

public set sunX(value) {
if (this._atmosphericScatteringSky.setting.sunX != value) {
this._atmosphericScatteringSky.setting.sunX = value;
if (this._relatedTransform) {
this._relatedTransform.rotationY = value * 360 - 90;
}
this._onChange = true;
}
}

public get sunY() {
return this._atmosphericScatteringSky.setting.sunY;
}

public set sunY(value) {
if (this._atmosphericScatteringSky.setting.sunY != value) {
this._atmosphericScatteringSky.setting.sunY = value;
if (this._relatedTransform) {
this._relatedTransform.rotationX = (value - 0.5) * 180;
}
this._onChange = true;
}
}

public get eyePos() {
return this._atmosphericScatteringSky.setting.eyePos;
}

public set eyePos(value) {
if (this._atmosphericScatteringSky.setting.eyePos != value) {
this._atmosphericScatteringSky.setting.eyePos = value;
this._onChange = true;
}
}

public get sunRadius() {
return this._atmosphericScatteringSky.setting.sunRadius;
}

public set sunRadius(value) {
if (this._atmosphericScatteringSky.setting.sunRadius != value) {
this._atmosphericScatteringSky.setting.sunRadius = value;
this._onChange = true;
}
}

public get sunRadiance() {
return this._atmosphericScatteringSky.setting.sunRadiance;
}

public set sunRadiance(value) {
if (this._atmosphericScatteringSky.setting.sunRadiance != value) {
this._atmosphericScatteringSky.setting.sunRadiance = value;
this._onChange = true;
}
}

public get sunBrightness() {
return this._atmosphericScatteringSky.setting.sunBrightness;
}

public set sunBrightness(value) {
if (this._atmosphericScatteringSky.setting.sunBrightness != value) {
this._atmosphericScatteringSky.setting.sunBrightness = value;
this._onChange = true;
}
}

public get displaySun() {
return this._atmosphericScatteringSky.setting.displaySun;
}

public set displaySun(value) {
if (this._atmosphericScatteringSky.setting.displaySun != value) {
this._atmosphericScatteringSky.setting.displaySun = value;
this._onChange = true;
}
}

public get enableClouds() {
return this._atmosphericScatteringSky.setting.enableClouds;
}

public set enableClouds(value) {
if (this._atmosphericScatteringSky.setting.enableClouds != value) {
this._atmosphericScatteringSky.setting.enableClouds = value;
this._onChange = true;
}
}

public get showV1() {
return this._atmosphericScatteringSky.setting.showV1;
}

public set showV1(value) {
if (this._atmosphericScatteringSky.setting.showV1 != value) {
this._atmosphericScatteringSky.setting.showV1 = value;
this._onChange = true;
}
}

public get hdrExposure() {
return this._atmosphericScatteringSky.setting.hdrExposure;
}

public set hdrExposure(value) {
if (this._atmosphericScatteringSky.setting.hdrExposure != value) {
this._atmosphericScatteringSky.setting.hdrExposure = value;
this._onChange = true;
}
}


public init(): void {
super.init();
this._historyData = new HistoryData();
ShaderLib.register('AtmosphericScatteringIntegration', AtmosphericScatteringSky_shader.integration);
ShaderLib.register('AtmosphereEarth', AtmosphericScatteringSky_shader.earth);
ShaderLib.register('AtmosphereUniforms', AtmosphericScatteringSky_shader.uniforms);
this._atmosphericScatteringSky = new AtmosphericScatteringSky(new AtmosphericScatteringSkySetting());

let view3D = this.transform.view3D;
let scene = this.transform.scene3D;
this.map = this._atmosphericScatteringSky;
scene.envMap = this._atmosphericScatteringSky;
scene.envMap.isHDRTexture = true;
this.onUpdate(view3D);
}

public start(view?: any): void {
let scene = this.transform.scene3D;
this.map = this._atmosphericScatteringSky;
scene.envMap = this._atmosphericScatteringSky;
scene.envMap.isHDRTexture = true;
super.start();
}

public get relativeTransform() {
return this._relatedTransform;
}

public set relativeTransform(value: Transform) {
this._relatedTransform = value;
this._historyData.reset();
}

public onUpdate(view?: any) {
if (this._relatedTransform) {
this._relatedTransform.rotationZ = 0;
if (this._historyData.isRotateChange(this._relatedTransform.rotationX, this._relatedTransform.rotationY)) {
this.sunX = (this._relatedTransform.rotationY + 90) / 360//
this.sunY = this._relatedTransform.rotationX / 180 + 0.5;
} else if (this._historyData.isSkyChange(this.sunX, this.sunY)) {
this._relatedTransform.rotationY = this.sunX * 360 - 90;
this._relatedTransform.rotationX = (this.sunY - 0.5) * 180;
}
this._historyData.save(this.sunX, this.sunY, this._relatedTransform.rotationX, this._relatedTransform.rotationY);
}

if (this._onChange) {
this._onChange = false;
this._atmosphericScatteringSky.apply(view);
}

}

public destroy(force?: boolean): void {
super.destroy(force);
this._atmosphericScatteringSky.destroy();
this._atmosphericScatteringSky = null;
this._onChange = null;
}
}
3 changes: 3 additions & 0 deletions packages/atmosphere/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './component/AtmosphericComponent';
export * from './renderer/SkyRenderer';
export * from './textures/AtmosphericScatteringSky';
26 changes: 26 additions & 0 deletions packages/atmosphere/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@orillusion/atmosphere",
"version": "0.1.0",
"author": "Orillusion",
"description": "Orillusion Atmosphere Plugin",
"main": "./dist/atmosphere.umd.js",
"module": "./dist/atmosphere.es.js",
"module:dev": "./index.ts",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "vite build && npm run build:types && npm run build:clean",
"build:types": "tsc --emitDeclarationOnly -p tsconfig.json",
"build:clean": "mv dist/packages/atmosphere/* dist && rm -rf dist/src && rm -rf dist/packages"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/Orillusion/orillusion.git"
},
"dependencies": {
"@orillusion/core": "^0.8.0"
}
}
104 changes: 104 additions & 0 deletions packages/atmosphere/renderer/SkyRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
Engine3D,
View3D,
MeshRenderer,
BoundingBox,
Texture,
EntityCollect,
ClusterLightingBuffer,
RendererMask,
RendererPassState,
PassType,
SkyMaterial,
Vector3,
SphereGeometry
} from "@orillusion/core";

/**
*
* Sky Box Renderer Component
* @group Components
*/
export class SkyRenderer extends MeshRenderer {
/**
* The material used in the Sky Box.
*/
public skyMaterial: SkyMaterial;


public init(): void {
super.init();
this.castShadow = false;
this.castGI = true;
this.addRendererMask(RendererMask.Sky);
this.alwaysRender = true;

this.object3D.bound = new BoundingBox(Vector3.ZERO.clone(), Vector3.MAX);
this.geometry = new SphereGeometry(Engine3D.setting.sky.defaultFar, 20, 20);
this.skyMaterial ||= new SkyMaterial();
}

public onEnable(): void {
if (!this._readyPipeline) {
this.initPipeline();
} else {
this.castNeedPass();

if (!this._inRenderer && this.transform.scene3D) {
EntityCollect.instance.sky = this;
this._inRenderer = true;
}
}
}

public onDisable(): void {
if (this._inRenderer && this.transform.scene3D) {
this._inRenderer = false;
EntityCollect.instance.sky = null;
}
super.onDisable();
}

public renderPass2(view: View3D, passType: PassType, rendererPassState: RendererPassState, clusterLightingBuffer: ClusterLightingBuffer, encoder: GPURenderPassEncoder, useBundle: boolean = false) {
// this.transform.updateWorldMatrix();
super.renderPass2(view, passType, rendererPassState, clusterLightingBuffer, encoder, useBundle);
// this.transform.localPosition = Camera3D.mainCamera.transform.localPosition ;
}

/**
* set environment texture
*/
public set map(texture: Texture) {
this.skyMaterial.baseMap = texture;
if (this.skyMaterial.name == null) {
this.skyMaterial.name = 'skyMaterial';
}
this.material = this.skyMaterial;
}

/**
* get environment texture
*/
public get map(): Texture {
return this.skyMaterial.baseMap;
}

public get exposure() {
return this.skyMaterial.exposure;
}

public set exposure(value) {
if (this.skyMaterial)
this.skyMaterial.exposure = value;
}

public get roughness() {
return this.skyMaterial.roughness;
}

public set roughness(value) {
if (this.skyMaterial)
this.skyMaterial.roughness = value;
}

}
111 changes: 111 additions & 0 deletions packages/atmosphere/shader/AtmosphereEarth.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
fn GetAtmosphereParameters() -> AtmosphereParameters {
var info: AtmosphereParameters;

let EarthBottomRadius: f32 = 6360.0;
var scalar: f32 = 1.0; // TODO: control with uniform
var EarthTopRadius: f32 = EarthBottomRadius + 100.0 * scalar;
var EarthRayleighScaleHeight: f32 = 8.0 * scalar;
var EarthMieScaleHeight: f32 = 1.2 * scalar;

info.BottomRadius = EarthBottomRadius;
info.TopRadius = EarthTopRadius;
info.GroundAlbedo = vec3<f32>(1.0, 1.0, 1.0);

info.RayleighDensityExpScale = -1.0 / EarthRayleighScaleHeight;
info.RayleighScattering = vec3<f32>(0.005802, 0.013558, 0.033100);

info.MieDensityExpScale = -1.0 / EarthMieScaleHeight;
info.MieScattering = vec3<f32>(0.003996, 0.003996, 0.003996);
info.MieExtinction = vec3<f32>(0.004440, 0.004440, 0.004440);
info.MieAbsorption = info.MieExtinction - info.MieScattering;
info.MiePhaseG = 0.8;

info.AbsorptionDensity0LayerWidth = 25.0 * scalar;
info.AbsorptionDensity0ConstantTerm = -2.0 / 3.0;
info.AbsorptionDensity0LinearTerm = 1.0 / (15.0 * scalar);
info.AbsorptionDensity1ConstantTerm = 8.0 / 3.0;
info.AbsorptionDensity1LinearTerm = -1.0 / (15.0 * scalar);
info.AbsorptionExtinction = vec3<f32>(0.000650, 0.001881, 0.000085);

// Cloud parameters
info.CloudBaseHeight = 3.0; // Base height of clouds in km
info.CloudTopHeight = 5.0; // Top height of clouds in km
info.CloudScattering = vec3<f32>(0.9, 0.9, 0.9); // Albedo of clouds
info.CloudAbsorption = vec3<f32>(0.001, 0.001, 0.001);
info.CloudPhaseG = 0.8;
info.CloudK = 0.5;

return info;
}

fn getAlbedo(scattering: vec3<f32>, extinction: vec3<f32>) -> vec3<f32> {
return vec3<f32>(
scattering.x / max(0.001, extinction.x),
scattering.y / max(0.001, extinction.y),
scattering.z / max(0.001, extinction.z)
);
}

fn sampleMediumRGB(WorldPos: vec3<f32>, Atmosphere: AtmosphereParameters) -> MediumSampleRGB {
var viewHeight: f32 = length(WorldPos) - Atmosphere.BottomRadius;

var densityMie: f32 = exp(Atmosphere.MieDensityExpScale * viewHeight);
var densityRay: f32 = exp(Atmosphere.RayleighDensityExpScale * viewHeight);
var clampVal: f32 = Atmosphere.AbsorptionDensity1LinearTerm * viewHeight + Atmosphere.AbsorptionDensity1ConstantTerm;
if viewHeight < Atmosphere.AbsorptionDensity0LayerWidth {
clampVal = Atmosphere.AbsorptionDensity0LinearTerm * viewHeight + Atmosphere.AbsorptionDensity0ConstantTerm;
}
var densityOzo: f32 = clamp(clampVal, 0.0, 1.0);

var s: MediumSampleRGB;

s.scatteringMie = densityMie * Atmosphere.MieScattering;
s.absorptionMie = densityMie * Atmosphere.MieAbsorption;
s.extinctionMie = densityMie * Atmosphere.MieExtinction;

s.scatteringRay = densityRay * Atmosphere.RayleighScattering;
s.absorptionRay = vec3<f32>(0.0, 0.0, 0.0);
s.extinctionRay = s.scatteringRay + s.absorptionRay;

s.scatteringOzo = vec3<f32>(0.0, 0.0, 0.0);
s.absorptionOzo = densityOzo * Atmosphere.AbsorptionExtinction;
s.extinctionOzo = s.scatteringOzo + s.absorptionOzo;

if uniformBuffer.enableClouds > .5 {
var cloudDensity: f32 = sampleCloudDensity(WorldPos, Atmosphere);
s.scatteringCloud = cloudDensity * Atmosphere.CloudScattering;
s.absorptionCloud = cloudDensity * Atmosphere.CloudAbsorption;
s.extinctionCloud = s.scatteringCloud + s.absorptionCloud;
}

s.scattering = s.scatteringMie + s.scatteringRay + s.scatteringOzo + s.scatteringCloud;
s.absorption = s.absorptionMie + s.absorptionRay + s.absorptionOzo + s.absorptionCloud;
s.extinction = s.extinctionMie + s.extinctionRay + s.extinctionOzo + s.extinctionCloud;
s.albedo = getAlbedo(s.scattering, s.extinction);

return s;
}

fn sigmoid(x: f32) -> f32 {
return 1.0 / (1.0 + exp(-x));
}

fn sampleCloudDensity(WorldPos: vec3<f32>, Atmosphere: AtmosphereParameters) -> f32 {
var x: f32 = length(WorldPos) - Atmosphere.BottomRadius;
var ymin: f32 = Atmosphere.CloudBaseHeight;
var ymax: f32 = Atmosphere.CloudTopHeight;
var yt: f32 = 1.0;
var baseVal: f32 = smoothstep(ymin, ymin + yt, x) * (1. - smoothstep(ymax - yt, ymax, x));
var noiseScale: f32 = 1. / 11000.;
var uv0: vec2<f32> = WorldPos.xz * noiseScale;
var noiseValue: f32 = sampleCloudTexture(uv0);
var noiseExpression: f32 = 12.;
var noiseFactor: f32 = sigmoid(noiseValue * 2.0 * noiseExpression - noiseExpression);
return baseVal * noiseFactor;
}

// sample the cloud texture
fn sampleCloudTexture(uv: vec2<f32>) -> f32 {
var coords = vec2<f32>(uv * vec2<f32>(textureDimensions(cloudTexture, 0)));
return textureSampleLevel(cloudTexture, cloudTextureSampler, coords, 0).r;
}
16 changes: 16 additions & 0 deletions packages/atmosphere/shader/AtmosphereUniforms.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
struct UniformData {
width: f32,
height: f32,
sunU: f32,
sunV: f32,
eyePos: f32,
sunRadius: f32, // = 500.0;
sunRadiance: f32, // = 20.0;
mieG: f32, // = 0.76;
mieHeight: f32, // = 1200;
sunBrightness: f32, // = 1.0;
displaySun: f32, // > 0.5: true
enableClouds: f32, // > 0.5: true
hdrExposure: f32, // = 1.0;
skyColor: vec4<f32>, // sky color
};
429 changes: 429 additions & 0 deletions packages/atmosphere/shader/AtmosphericScatteringIntegration.wgsl

Large diffs are not rendered by default.

258 changes: 258 additions & 0 deletions packages/atmosphere/shader/AtmosphericScatteringSky.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
#include 'ColorUtil_frag'
#include 'AtmosphereUniforms'

@group(0) @binding(0) var<uniform> uniformBuffer: UniformData;
@group(0) @binding(1) var outTexture : texture_storage_2d<rgba16float, write>;

var<private> uv01: vec2<f32>;
var<private> fragCoord: vec2<i32>;
var<private> texSizeF32: vec2<f32>;

var<private> PI:f32 = 3.1415926535;
var<private> PI_2:f32 = 0.0;
var<private> EPSILON:f32 = 0.0000001;
var<private> SAMPLES_NUMS:i32 = 16;

var<private> transmittance:vec3<f32>;
var<private> insctrMie:vec3<f32>;
var<private> insctrRayleigh:vec3<f32>;

@compute @workgroup_size( 8 , 8 , 1 )
fn CsMain(@builtin(workgroup_id) workgroup_id: vec3<u32>, @builtin(global_invocation_id) globalInvocation_id: vec3<u32>) {
fragCoord = vec2<i32>(globalInvocation_id.xy);
texSizeF32 = vec2<f32>(uniformBuffer.width, uniformBuffer.height);
uv01 = vec2<f32>(globalInvocation_id.xy) / texSizeF32;
uv01.y = 1.0 - uv01.y - EPSILON;
PI_2 = PI * 2.0;
textureStore(outTexture, fragCoord, mainImage(uv01));//vec4(uv01, 0.0, 1.0));
}

struct ScatteringParams {
sunRadius: f32,
sunRadiance: f32,

mieG: f32,
mieHeight: f32,

rayleighHeight: f32,

waveLambdaMie: vec3<f32>,
waveLambdaOzone: vec3<f32>,
waveLambdaRayleigh: vec3<f32>,

earthRadius: f32,
earthAtmTopRadius: f32,
earthCenter: vec3<f32>,
}

fn ComputeSphereNormal(coord: vec2<f32>, phiStart: f32, phiLength: f32, thetaStart: f32, thetaLength: f32) -> vec3<f32> {
var normal: vec3<f32>;
normal.x = -sin(thetaStart + coord.y * thetaLength) * sin(phiStart + coord.x * phiLength);
normal.y = -cos(thetaStart + coord.y * thetaLength);
normal.z = -sin(thetaStart + coord.y * thetaLength) * cos(phiStart + coord.x * phiLength);
return normalize(normal);
}

fn ComputeRaySphereIntersection(position: vec3<f32>, dir: vec3<f32>, center: vec3<f32>, radius: f32) -> vec2<f32> {
var origin: vec3<f32> = position - center;
var B = dot(origin, dir);
var C = dot(origin, origin) - radius * radius;
var D = B * B - C;

var minimaxIntersections: vec2<f32>;
if D < 0.0 {
minimaxIntersections = vec2<f32>(-1.0, -1.0);
} else {
D = sqrt(D);
minimaxIntersections = vec2<f32>(-B - D, -B + D);
}

return minimaxIntersections;
}

fn ComputeWaveLambdaRayleigh(lambda: vec3<f32>) -> vec3<f32> {
var n: f32 = 1.0003;
var N: f32 = 2.545E25;
var pn: f32 = 0.035;
var n2: f32 = n * n;
var pi3: f32 = PI * PI * PI;
var rayleighConst: f32 = (8.0 * pi3 * pow(n2 - 1.0, 2.0)) / (3.0 * N) * ((6.0 + 3.0 * pn) / (6.0 - 7.0 * pn));
return vec3<f32>(rayleighConst) / (lambda * lambda * lambda * lambda);
}

fn ComputePhaseMie(theta: f32, g: f32) -> f32 {
var g2 = g * g;
return (1.0 - g2) / pow(1.0 + g2 - 2.0 * g * saturate(theta), 1.5) / (4.0 * PI);
}

fn ComputePhaseRayleigh(theta: f32) -> f32 {
var theta2 = theta * theta;
return (theta2 * 0.75 + 0.75) / (4.0 * PI);
}

fn ChapmanApproximation(X: f32, h: f32, cosZenith: f32) -> f32 {
var c = sqrt(X + h);
var c_exp_h = c * exp(-h);

if cosZenith >= 0.0 {
return c_exp_h / (c * cosZenith + 1.0);
} else {
var x0 = sqrt(1.0 - cosZenith * cosZenith) * (X + h);
var c0 = sqrt(x0);

return 2.0 * c0 * exp(X - x0) - c_exp_h / (1.0 - c * cosZenith);
}
}

fn GetOpticalDepthSchueler(h: f32, H: f32, earthRadius: f32, cosZenith: f32) -> f32 {
return H * ChapmanApproximation(earthRadius / H, h / H, cosZenith);
}

fn GetTransmittance(setting: ScatteringParams, L: vec3<f32>, V: vec3<f32>) -> vec3<f32> {
var ch = GetOpticalDepthSchueler(L.y, setting.rayleighHeight, setting.earthRadius, V.y);
return exp(-(setting.waveLambdaMie + setting.waveLambdaRayleigh) * ch);
}

fn ComputeOpticalDepth(setting: ScatteringParams, samplePoint: vec3<f32>, V: vec3<f32>, L: vec3<f32>, neg: f32) -> vec2<f32> {
var rl = length(samplePoint);
var h = rl - setting.earthRadius;
var r: vec3<f32> = samplePoint / rl;

var cos_chi_sun = dot(r, L);
var cos_chi_ray = dot(r, V * neg);

var opticalDepthSun = GetOpticalDepthSchueler(h, setting.rayleighHeight, setting.earthRadius, cos_chi_sun);
var opticalDepthCamera = GetOpticalDepthSchueler(h, setting.rayleighHeight, setting.earthRadius, cos_chi_ray) * neg;

return vec2<f32>(opticalDepthSun, opticalDepthCamera);
}

fn AerialPerspective(setting: ScatteringParams, start: vec3<f32>, end: vec3<f32>, V: vec3<f32>, L: vec3<f32>, infinite: i32) {
var inf_neg: f32 = 1.0;
if infinite == 0 {
inf_neg = -1.0;
}

var sampleStep: vec3<f32> = (end - start) / f32(SAMPLES_NUMS);
var samplePoint: vec3<f32> = end - sampleStep;
var sampleLambda: vec3<f32> = setting.waveLambdaMie + setting.waveLambdaRayleigh + setting.waveLambdaOzone;

var sampleLength: f32 = length(sampleStep);

var scattering: vec3<f32> = vec3<f32>(0.0);
var lastOpticalDepth: vec2<f32> = ComputeOpticalDepth(setting, end, V, L, inf_neg);

for (var i: i32 = 1; i < SAMPLES_NUMS; i = i + 1) {
var opticalDepth: vec2<f32> = ComputeOpticalDepth(setting, samplePoint, V, L, inf_neg);

var segment_s: vec3<f32> = exp(-sampleLambda * (opticalDepth.x + lastOpticalDepth.x));
var segment_t: vec3<f32> = exp(-sampleLambda * (opticalDepth.y - lastOpticalDepth.y));

transmittance *= segment_t;

scattering = scattering * segment_t;
scattering += exp(-(length(samplePoint) - setting.earthRadius) / setting.rayleighHeight) * segment_s;

lastOpticalDepth = opticalDepth;
samplePoint = samplePoint - sampleStep;
}

insctrMie = scattering * setting.waveLambdaMie * sampleLength;
insctrRayleigh = scattering * setting.waveLambdaRayleigh * sampleLength;
}

fn ComputeSkyboxChapman(setting: ScatteringParams, eye: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> f32 {
var neg: i32 = 1;
var outerIntersections: vec2<f32> = ComputeRaySphereIntersection(eye, V, setting.earthCenter, setting.earthAtmTopRadius);
if outerIntersections.y < 0.0 {
return 0.0;
}
var innerIntersections: vec2<f32> = ComputeRaySphereIntersection(eye, V, setting.earthCenter, setting.earthRadius);
if innerIntersections.x > 0.0 {
neg = 0;
outerIntersections.y = innerIntersections.x;
}

let eye0 = eye - setting.earthCenter;

var start: vec3<f32> = eye0 + V * max(0.0, outerIntersections.x);
var end: vec3<f32> = eye0 + V * outerIntersections.y;

AerialPerspective(setting, start, end, V, L, neg);

//bool intersectionTest = innerIntersections.x < 0.0 && innerIntersections.y < 0.0;
//return intersectionTest ? 1.0 : 0.0;

if innerIntersections.x < 0.0 && innerIntersections.y < 0.0 {
return 1.0;
}
return 0.0;
}

fn ComputeSkyInscattering(setting: ScatteringParams, eye: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> vec4<f32> {
transmittance = vec3<f32>(1.0);
insctrMie = vec3<f32>(0.0);
insctrRayleigh = vec3<f32>(0.0);
var intersectionTest: f32 = ComputeSkyboxChapman(setting, eye, V, L);

var phaseTheta = dot(V, L);
var phaseMie = ComputePhaseMie(phaseTheta, setting.mieG);
var phaseRayleigh = ComputePhaseRayleigh(phaseTheta);
var phaseNight = 1.0 - saturate(transmittance.x * EPSILON);

var insctrTotalMie: vec3<f32> = insctrMie * phaseMie;
var insctrTotalRayleigh: vec3<f32> = insctrRayleigh * phaseRayleigh;

var sky: vec3<f32> = (insctrTotalMie + insctrTotalRayleigh) * setting.sunRadiance;
if uniformBuffer.displaySun > 0.5 {
var angle: f32 = saturate((1.0 - phaseTheta) * setting.sunRadius);
var cosAngle: f32 = cos(angle * PI * 0.5);
var edge: f32 = 0.0;
if angle >= 0.9 {
edge = smoothstep(0.9, 1.0, angle);
}

var limbDarkening: vec3<f32> = GetTransmittance(setting, -L, V);
limbDarkening *= pow(vec3<f32>(cosAngle), vec3<f32>(0.420, 0.503, 0.652)) * mix(vec3<f32>(1.0), vec3<f32>(1.2, 0.9, 0.5), edge) * intersectionTest;
sky += limbDarkening * uniformBuffer.sunBrightness;
}
return vec4<f32>(sky, phaseNight * intersectionTest);
}

fn noise(uv: vec2<f32>) -> f32 {
return fract(dot(sin(vec3<f32>(uv.xyx) * vec3<f32>(uv.xyy) * 1024.0), vec3<f32>(341896.483, 891618.637, 602649.7031)));
}

fn mainImage(uv: vec2<f32>) -> vec4<f32> {
let eyePosition = uniformBuffer.eyePos;
var sun = vec2<f32>(uniformBuffer.sunU, uniformBuffer.sunV);
var V: vec3<f32> = ComputeSphereNormal(uv, 0.0, PI_2, 0.0, PI);
var L: vec3<f32> = ComputeSphereNormal(vec2<f32>(sun.x, sun.y), 0.0, PI_2, 0.0, PI);

var setting: ScatteringParams;
setting.sunRadius = uniformBuffer.sunRadius;//500.0;
setting.sunRadiance = uniformBuffer.sunRadiance;//20.0;
setting.mieG = uniformBuffer.mieG;//0.76;
setting.mieHeight = uniformBuffer.mieHeight;// 1200.0;
setting.rayleighHeight = 8000.0;
setting.earthRadius = 6360000.0;
setting.earthAtmTopRadius = 6420000.0;
setting.earthCenter = vec3<f32>(0, -setting.earthRadius, 0);
setting.waveLambdaMie = vec3<f32>(0.0000002);

// wavelength with 680nm, 550nm, 450nm
setting.waveLambdaRayleigh = ComputeWaveLambdaRayleigh(vec3<f32>(0.000000680, 0.000000550, 0.000000450));

// see https://www.shadertoy.com/view/MllBR2
setting.waveLambdaOzone = vec3<f32>(1.36820899679147, 3.31405330400124, 0.13601728252538) * 0.0000006 * 2.504;

var eye: vec3<f32> = vec3<f32>(0, eyePosition, 0);
var sky0: vec4<f32> = ComputeSkyInscattering(setting, eye, V, L);
var sky = vec3<f32>(sky0.rgb);

sky = ACESToneMapping(sky.rgb, uniformBuffer.hdrExposure);
sky = pow(sky.rgb, vec3<f32>(1.0 / 1.2)); // gamma

var fragColor: vec4<f32> = vec4<f32>((sky.rgb), 1.0);
return fragColor;
}
24 changes: 24 additions & 0 deletions packages/atmosphere/shader/AtmosphericScatteringSky_shader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import source from "./AtmosphericScatteringSky.wgsl?raw";
import transmittance from "./RenderTransmittanceLutPS.wgsl?raw";
import integration from "./AtmosphericScatteringIntegration.wgsl?raw";
import multiscatter from "./NewMultiScattCS.wgsl?raw";
import earth from "./AtmosphereEarth.wgsl?raw";
import uniforms from "./AtmosphereUniforms.wgsl?raw";
import raymarch from "./RenderSkyRayMarching.wgsl?raw";
import skyview from "./SkyViewLutPS.wgsl?raw";
import cloud from "./CloudNoise.wgsl?raw";

/**
* @internal
*/
export class AtmosphericScatteringSky_shader {
public static cs: string = source;
public static transmittance_cs: string = transmittance;
public static multiscatter_cs: string = multiscatter;
public static integration: string = integration;
public static raymarch_cs: string = raymarch;
public static skyview_cs: string = skyview;
public static earth: string = earth;
public static uniforms: string = uniforms;
public static cloud_cs: string = cloud;
}
45 changes: 45 additions & 0 deletions packages/atmosphere/shader/CloudNoise.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include 'AtmosphereUniforms'

@group(0) @binding(0) var<uniform> uniformBuffer: UniformData;
@group(0) @binding(1) var outTexture: texture_storage_2d<rgba16float, write>;

var<private> uv01: vec2<f32>;
var<private> fragCoord: vec2<i32>;
var<private> texSizeF32: vec2<f32>;
var<private> PI: f32 = 3.1415926535897932384626433832795;
var<private> PI_2: f32 = 0.0;
var<private> EPSILON:f32 = 0.0000001;

@compute @workgroup_size(8, 8, 1)
fn CsMain(@builtin(workgroup_id) workgroup_id: vec3<u32>, @builtin(global_invocation_id) globalInvocation_id: vec3<u32>) {
fragCoord = vec2<i32>(globalInvocation_id.xy);
texSizeF32 = vec2<f32>(uniformBuffer.width, uniformBuffer.height);
uv01 = vec2<f32>(globalInvocation_id.xy) / texSizeF32;
uv01.y = 1.0 - uv01.y - EPSILON;
PI_2 = PI * 2.0;
uv01 *= vec2<f32>(10., 10.);
var coord = vec3<f32>(uv01, 1.0);
var noiseValue = 1. - worleyNoise3D(coord);
var outColor = vec4<f32>(noiseValue, noiseValue, noiseValue, 1.);
textureStore(outTexture, fragCoord, outColor);
}

fn worleyNoise3D(pos: vec3<f32>) -> f32 {
var minDist: f32 = 1.0; // Large number to start with
for (var z: i32 = -1; z <= 1; z++) {
for (var y: i32 = -1; y <= 1; y++) {
for (var x: i32 = -1; x <= 1; x++) {
var cell = floor(pos) + vec3<f32>(f32(x), f32(y), f32(z));
var point = cell + hash3D(cell); // hash3D is a function you need to define
var dist = distance(pos, point);
minDist = min(minDist, dist);
}
}
}
return minDist;
}

fn hash3D(p: vec3<f32>) -> vec3<f32> {
// Simple hash function, replace with a better one if needed
return fract(sin(vec3<f32>(dot(p, vec3<f32>(127.1, 311.7, 74.7)), dot(p, vec3<f32>(269.5, 183.3, 246.1)), dot(p, vec3<f32>(113.5, 271.9, 124.6)))) * 43758.5453);
}
127 changes: 127 additions & 0 deletions packages/atmosphere/shader/NewMultiScattCS.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include 'AtmosphereEarth'
#include 'AtmosphericScatteringIntegration'
#include 'AtmosphereUniforms'

@group(0) @binding(0) var<uniform> uniformBuffer: UniformData;
@group(0) @binding(1) var outTexture: texture_storage_2d<rgba16float, write>;
@group(0) @binding(auto) var transmittanceTexture: texture_2d<f32>;
@group(0) @binding(auto) var transmittanceTextureSampler: sampler;
@group(0) @binding(auto) var multipleScatteringTexture: texture_2d<f32>;
@group(0) @binding(auto) var multipleScatteringTextureSampler: sampler;
@group(0) @binding(auto) var cloudTextureSampler: sampler;
@group(0) @binding(auto) var cloudTexture: texture_2d<f32>;

var<workgroup> MultiScatAs1SharedMem: array<vec3<f32>, 64>;
var<workgroup> LSharedMem: array<vec3<f32>, 64>;

var<private> PI: f32 = 3.1415926535897932384626433832795;
var<private> PI_2: f32 = 0.0;
var<private> EPSILON: f32 = 0.0000001;
// var<private> MULTI_SCATTERING_POWER_SERIE: u32 = 1;
var<private> SQRTSAMPLECOUNT: u32 = 8;

var<private> MultipleScatteringFactor: f32 = 1.0; // change to 50 to see the texture

@compute @workgroup_size(1, 1, 64)
fn CsMain(@builtin(global_invocation_id) ThreadId: vec3<u32>) {
var texSize = vec2<f32>(uniformBuffer.width, uniformBuffer.height);
var pixPos: vec2<f32> = vec2<f32>(ThreadId.xy) + 0.5;
var uv: vec2<f32> = pixPos / MultiScatteringLUTRes;

uv = vec2<f32>(fromSubUvsToUnit(uv.x, MultiScatteringLUTRes), fromSubUvsToUnit(1. - uv.y, MultiScatteringLUTRes));

var Atmosphere: AtmosphereParameters = GetAtmosphereParameters();

var cosSunZenithAngle: f32 = uv.x * 2.0 - 1.0;
var sunDir: vec3<f32> = vec3<f32>(0.0, sqrt(saturate(1.0 - cosSunZenithAngle * cosSunZenithAngle)), cosSunZenithAngle);
var viewHeight: f32 = Atmosphere.BottomRadius + saturate(uv.y + PLANET_RADIUS_OFFSET) * (Atmosphere.TopRadius - Atmosphere.BottomRadius - PLANET_RADIUS_OFFSET);

var WorldPos: vec3<f32> = vec3<f32>(0.0, 0.0, viewHeight);
var WorldDir: vec3<f32> = vec3<f32>(0.0, 0.0, 1.0);

const ground: bool = true;
const SampleCountIni: f32 = 20.0;
const DepthBufferValue: f32 = -1.0;
const VariableSampleCount: bool = false;
const MieRayPhase: bool = false;

var SphereSolidAngle: f32 = 4.0 * PI;
var IsotropicPhase: f32 = 1.0 / SphereSolidAngle;


var sqrtSample: f32 = f32(SQRTSAMPLECOUNT);
var i: f32 = 0.5 + f32(ThreadId.z / SQRTSAMPLECOUNT);
var j: f32 = 0.5 + f32(ThreadId.z - u32(f32(ThreadId.z / SQRTSAMPLECOUNT) * f32(SQRTSAMPLECOUNT)));
{
var randA: f32 = i / sqrtSample;
var randB: f32 = j / sqrtSample;
var theta: f32 = 2.0 * PI * randA;
var phi: f32 = acos(1.0 - 2.0 * randB);
var cosPhi: f32 = cos(phi);
var sinPhi: f32 = sin(phi);
var cosTheta: f32 = cos(theta);
var sinTheta: f32 = sin(theta);
WorldDir.x = cosTheta * sinPhi;
WorldDir.y = sinTheta * sinPhi;
WorldDir.z = cosPhi;
var result: SingleScatteringResult = IntegrateScatteredLuminance(pixPos, WorldPos, WorldDir, sunDir, Atmosphere, ground, SampleCountIni, DepthBufferValue, VariableSampleCount, MieRayPhase, defaultTMaxMax, texSize);

MultiScatAs1SharedMem[ThreadId.z] = result.MultiScatAs1 * SphereSolidAngle / (sqrtSample * sqrtSample);
LSharedMem[ThreadId.z] = result.L * SphereSolidAngle / (sqrtSample * sqrtSample);
}

workgroupBarrier();

if ThreadId.z < 32 {
MultiScatAs1SharedMem[ThreadId.z] += MultiScatAs1SharedMem[ThreadId.z + 32];
LSharedMem[ThreadId.z] += LSharedMem[ThreadId.z + 32];
}
workgroupBarrier();

if ThreadId.z < 16 {
MultiScatAs1SharedMem[ThreadId.z] += MultiScatAs1SharedMem[ThreadId.z + 16];
LSharedMem[ThreadId.z] += LSharedMem[ThreadId.z + 16];
}
workgroupBarrier();

if ThreadId.z < 8 {
MultiScatAs1SharedMem[ThreadId.z] += MultiScatAs1SharedMem[ThreadId.z + 8];
LSharedMem[ThreadId.z] += LSharedMem[ThreadId.z + 8];
}
workgroupBarrier();
if ThreadId.z < 4 {
MultiScatAs1SharedMem[ThreadId.z] += MultiScatAs1SharedMem[ThreadId.z + 4];
LSharedMem[ThreadId.z] += LSharedMem[ThreadId.z + 4];
}
workgroupBarrier();
if ThreadId.z < 2 {
MultiScatAs1SharedMem[ThreadId.z] += MultiScatAs1SharedMem[ThreadId.z + 2];
LSharedMem[ThreadId.z] += LSharedMem[ThreadId.z + 2];
}
workgroupBarrier();
if ThreadId.z < 1 {
MultiScatAs1SharedMem[ThreadId.z] += MultiScatAs1SharedMem[ThreadId.z + 1];
LSharedMem[ThreadId.z] += LSharedMem[ThreadId.z + 1];
}
workgroupBarrier();
if ThreadId.z > 0 {
return;
}

var MultiScatAs1: vec3<f32> = MultiScatAs1SharedMem[0] * IsotropicPhase;
var InScatteredLuminance: vec3<f32> = LSharedMem[0] * IsotropicPhase;

var L: vec3<f32> = vec3<f32>(0.0, 0.0, 0.0);
if MULTI_SCATTERING_POWER_SERIE == 0 {
var MultiScatAs1SQR: vec3<f32> = MultiScatAs1 * MultiScatAs1;
L = InScatteredLuminance * (1.0 + MultiScatAs1 + MultiScatAs1SQR + MultiScatAs1 * MultiScatAs1SQR + MultiScatAs1SQR * MultiScatAs1SQR);
} else {
var r: vec3<f32> = MultiScatAs1;
var SumOfAllMultiScatteringEventsContribution: vec3<f32> = 1.0 / (1.0 - r);
L = InScatteredLuminance * SumOfAllMultiScatteringEventsContribution;
}

var fragColor = vec4<f32>(MultipleScatteringFactor * L, 1.0);
var fragCoord = vec2<i32>(ThreadId.xy);
textureStore(outTexture, fragCoord, fragColor);
}
75 changes: 75 additions & 0 deletions packages/atmosphere/shader/RenderSkyRayMarching.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include 'AtmosphereEarth'
#include 'AtmosphericScatteringIntegration'
#include 'AtmosphereUniforms'
#include 'ColorUtil_frag'

@group(0) @binding(0) var<uniform> uniformBuffer: UniformData;
@group(0) @binding(1) var outTexture: texture_storage_2d<rgba16float, write>;

@group(0) @binding(auto) var multipleScatteringTexture: texture_2d<f32>;
@group(0) @binding(auto) var multipleScatteringTextureSampler: sampler;

@group(0) @binding(auto) var transmittanceTexture: texture_2d<f32>;
@group(0) @binding(auto) var transmittanceTextureSampler: sampler;

@group(0) @binding(auto) var cloudTextureSampler: sampler;
@group(0) @binding(auto) var cloudTexture: texture_2d<f32>;

var<private> uv01: vec2<f32>;
var<private> fragCoord: vec2<i32>;
var<private> texSize: vec2<f32>;

var<private> PI: f32 = 3.1415926535897932384626433832795;
var<private> PI_2: f32 = 0.0;
var<private> EPSILON: f32 = 0.0000001;
var<private> IS_HDR_SKY = false;

@compute @workgroup_size(8, 8, 1)
fn CsMain(@builtin(global_invocation_id) ThreadId: vec3<u32>) {
fragCoord = vec2<i32>(ThreadId.xy);
texSize = vec2<f32>(uniformBuffer.width, uniformBuffer.height);
uv01 = vec2<f32>(ThreadId.xy) / texSize;
uv01.y = 1.0 - uv01.y - EPSILON;
PI_2 = PI * 2.0;
textureStore(outTexture, fragCoord, mainImage(uv01, vec2<f32>(fragCoord)));
}

fn mainImage(uv: vec2<f32>, pixPos: vec2<f32>) -> vec4<f32> {
var coords = vec2<i32>(uv * vec2<f32>(textureDimensions(multipleScatteringTexture, 0)));
var sampleA = textureSampleLevel(multipleScatteringTexture, multipleScatteringTextureSampler, uv, 0).rgb;

coords = vec2<i32>(uv * vec2<f32>(textureDimensions(transmittanceTexture, 0)));
var sampleB = textureSampleLevel(transmittanceTexture, transmittanceTextureSampler, uv, 0).rgb;

// sample the cloud texture
coords = vec2<i32>(uv * vec2<f32>(textureDimensions(cloudTexture, 0)));
var sampleC = textureSampleLevel(cloudTexture, cloudTextureSampler, uv, 0).rgb;

var Atmosphere: AtmosphereParameters = GetAtmosphereParameters();

var eyePosition = uniformBuffer.eyePos;
var sun = vec2<f32>(uniformBuffer.sunU, uniformBuffer.sunV);
var WorldDir: vec3<f32> = ComputeSphereNormal(uv, 0.0, PI_2, 0.0, PI);
var SunDir: vec3<f32> = ComputeSphereNormal(vec2<f32>(sun.x, sun.y), 0.0, PI_2, 0.0, PI);
var WorldPos = vec3(0, Atmosphere.BottomRadius + eyePosition/1000.0 + 0.01, 0);

const ground = false;
const SampleCountIni = 30.0;
const DepthBufferValue = -1.0;
const VariableSampleCount = true;
const MieRayPhase = true;
var result: SingleScatteringResult = IntegrateScatteredLuminance(pixPos, WorldPos, WorldDir, SunDir, Atmosphere, ground, SampleCountIni, DepthBufferValue, VariableSampleCount, MieRayPhase, defaultTMaxMax, texSize);

// for HDR lighting
var sky: vec3<f32>;
if (IS_HDR_SKY) {
sky = LinearToGammaSpace(result.L) * uniformBuffer.hdrExposure;
} else {
// for LDR lighting
sky = result.L;
sky = ACESToneMapping(sky.rgb, uniformBuffer.hdrExposure);
sky = pow(sky.rgb, vec3<f32>(1.0/1.2)); // gamma
}

return vec4<f32>(sky, 1.0);
}
76 changes: 76 additions & 0 deletions packages/atmosphere/shader/RenderTransmittanceLutPS.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include 'AtmosphereEarth'
#include 'AtmosphericScatteringIntegration'
#include 'AtmosphereUniforms'

@group(0) @binding(0) var<uniform> uniformBuffer: UniformData;
@group(0) @binding(1) var outTexture: texture_storage_2d<rgba16float, write>;
@group(0) @binding(auto) var transmittanceTexture: texture_2d<f32>;
@group(0) @binding(auto) var transmittanceTextureSampler: sampler;
@group(0) @binding(auto) var multipleScatteringTexture: texture_2d<f32>;
@group(0) @binding(auto) var multipleScatteringTextureSampler: sampler;
@group(0) @binding(auto) var cloudTextureSampler: sampler;
@group(0) @binding(auto) var cloudTexture: texture_2d<f32>;

var<private> uv01: vec2<f32>;
var<private> fragCoord: vec2<i32>;
var<private> texSizeF32: vec2<f32>;
var<private> PI: f32 = 3.1415926535897932384626433832795;
var<private> PI_2: f32 = 0.0;
var<private> EPSILON:f32 = 0.0000001;

@compute @workgroup_size(8 , 8 , 1)
fn CsMain(@builtin(workgroup_id) workgroup_id: vec3<u32>, @builtin(global_invocation_id) globalInvocation_id: vec3<u32>) {
fragCoord = vec2<i32>(globalInvocation_id.xy);
texSizeF32 = vec2<f32>(uniformBuffer.width, uniformBuffer.height);
uv01 = vec2<f32>(globalInvocation_id.xy) / texSizeF32;
uv01.y = 1.0 - uv01.y - EPSILON;
PI_2 = PI * 2.0;
textureStore(outTexture, fragCoord, RenderTransmittanceLutPS(vec2<f32>(fragCoord), uv01));//vec4(uv01, 0.0, 1.0));
}

struct UvToLutResult {
viewHeight: f32,
viewZenithCosAngle: f32,
};

fn UvToLutTransmittanceParams(Atmosphere: AtmosphereParameters, uv: vec2<f32>) -> UvToLutResult {
var result: UvToLutResult;

var x_mu: f32 = uv.x;
var x_r: f32 = uv.y;

var H: f32 = sqrt(Atmosphere.TopRadius * Atmosphere.TopRadius - Atmosphere.BottomRadius * Atmosphere.BottomRadius);
var rho: f32 = H * x_r;
result.viewHeight = sqrt(rho * rho + Atmosphere.BottomRadius * Atmosphere.BottomRadius);

var d_min: f32 = Atmosphere.TopRadius - result.viewHeight;
var d_max: f32 = rho + H;
var d: f32 = d_min + x_mu * (d_max - d_min);
result.viewZenithCosAngle = (H * H - rho * rho - d * d) / (2.0 * result.viewHeight * d);
if d == 0.0 {
result.viewZenithCosAngle = 1.0;
}
result.viewZenithCosAngle = clamp(result.viewZenithCosAngle, -1.0, 1.0);

return result;
}

fn RenderTransmittanceLutPS(pixPos: vec2<f32>, uv: vec2<f32>) -> vec4<f32> {
var Atmosphere: AtmosphereParameters = GetAtmosphereParameters();
var transmittanceParams: UvToLutResult = UvToLutTransmittanceParams(Atmosphere, uv);

var WorldPos: vec3<f32> = vec3<f32>(0.0, 0.0, transmittanceParams.viewHeight);
var WorldDir: vec3<f32> = vec3<f32>(0.0, sqrt(1.0 - transmittanceParams.viewZenithCosAngle * transmittanceParams.viewZenithCosAngle), transmittanceParams.viewZenithCosAngle);

var ground = false;
var SampleCountIni = 40.0; // Can go a low as 10 sample but energy lost starts to be visible.
var DepthBufferValue = -1.0;
var VariableSampleCount = false;
var MieRayPhase = false;

var scatteringResult: SingleScatteringResult = IntegrateScatteredLuminance(pixPos, WorldPos, WorldDir, getSunDirection(), Atmosphere, ground, SampleCountIni, DepthBufferValue, VariableSampleCount, MieRayPhase, defaultTMaxMax, texSizeF32);
var transmittance: vec3<f32> = exp(-scatteringResult.OpticalDepth);

// Optical depth to transmittance
return vec4<f32>(transmittance, 1.0);
}
121 changes: 121 additions & 0 deletions packages/atmosphere/shader/SkyViewLutPS.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include 'AtmosphereEarth'
#include 'AtmosphericScatteringIntegration'
#include 'AtmosphereUniforms'

@group(0) @binding(0) var<uniform> uniformBuffer: UniformData;
@group(0) @binding(1) var outTexture: texture_storage_2d<rgba16float, write>;
@group(0) @binding(auto) var transmittanceTexture: texture_2d<f32>;
@group(0) @binding(auto) var transmittanceTextureSampler: sampler;
@group(0) @binding(auto) var multipleScatteringTexture: texture_2d<f32>;
@group(0) @binding(auto) var multipleScatteringTextureSampler: sampler;
@group(0) @binding(auto) var cloudTextureSampler: sampler;
@group(0) @binding(auto) var cloudTexture: texture_2d<f32>;

var<private> uv01: vec2<f32>;
var<private> fragCoord: vec2<i32>;
var<private> texSize: vec2<f32>;
var<private> PI: f32 = 3.1415926535897932384626433832795;
var<private> PI_2: f32 = 0.0;
var<private> EPSILON:f32 = 0.0000001;
var<private> NONLINEARSKYVIEWLUT: bool = true;

@compute @workgroup_size(8, 8, 1)
fn CsMain(@builtin(workgroup_id) workgroup_id: vec3<u32>, @builtin(global_invocation_id) globalInvocation_id: vec3<u32>) {
fragCoord = vec2<i32>(globalInvocation_id.xy);
texSize = vec2<f32>(uniformBuffer.width, uniformBuffer.height);
uv01 = vec2<f32>(globalInvocation_id.xy) / texSize;
uv01.y = 1.0 - uv01.y - EPSILON;
PI_2 = PI * 2.0;
textureStore(outTexture, fragCoord, SkyViewLutPS(vec2<f32>(fragCoord), uv01));//vec4(uv01, 0.0, 1.0));
}

fn UvToSkyViewLutParams(Atmosphere: AtmosphereParameters, viewHeight: f32, uv0: vec2<f32>) -> vec2<f32> {
// Constrain uvs to valid sub texel range (avoid zenith derivative issue making LUT usage visible)
var uv = vec2<f32>(fromSubUvsToUnit(uv0.x, 192.0), fromSubUvsToUnit(uv0.y, 108.0));

var Vhorizon: f32 = sqrt(viewHeight * viewHeight - Atmosphere.BottomRadius * Atmosphere.BottomRadius);
var CosBeta: f32 = Vhorizon / viewHeight; // GroundToHorizonCos
var Beta: f32 = acos(CosBeta);
var ZenithHorizonAngle: f32 = PI - Beta;
var viewZenithCosAngle: f32;

if uv.y < 0.5 {
var coord: f32 = 2.0 * uv.y;
coord = 1.0 - coord;
if NONLINEARSKYVIEWLUT {
coord = coord * coord;
}
coord = 1.0 - coord;
viewZenithCosAngle = cos(ZenithHorizonAngle * coord);
} else {
var coord: f32 = uv.y * 2.0 - 1.0;
if NONLINEARSKYVIEWLUT {
coord = coord * coord;
}
viewZenithCosAngle = cos(ZenithHorizonAngle + Beta * coord);
}

var coord: f32 = uv.x;
coord = coord * coord;
var lightViewCosAngle: f32 = -(coord * 2.0 - 1.0);

return vec2<f32>(viewZenithCosAngle, lightViewCosAngle);
}


fn SkyViewLutPS(pixPos: vec2<f32>, uv: vec2<f32>) -> vec4<f32> {
var Atmosphere: AtmosphereParameters = GetAtmosphereParameters();

// var ClipSpace: vec3<f32> = vec3<f32>((pixPos / vec2<f32>(192.0, 108.0)) * vec2<f32>(2.0, -2.0) - vec2<f32>(1.0, -1.0), 1.0);
// var HViewPos: vec4<f32> = uniformBuffer.skyInvProjMat * vec4<f32>(ClipSpace, 1.0);
// var m = uniformBuffer.skyInvViewMat;
// var WorldDir: vec3<f32> = normalize((mat3x3<f32>(m[0].xyz, m[1].xyz, m[2].xyz) * HViewPos.xyz) / HViewPos.w);
var WorldPos: vec3<f32> = vec3<f32>(0, Atmosphere.BottomRadius + uniformBuffer.eyePos + 0.01, 0);

var viewHeight: f32 = length(WorldPos);

var lutParams = UvToSkyViewLutParams(Atmosphere, viewHeight, uv);
var viewZenithCosAngle: f32 = lutParams.x;
var lightViewCosAngle: f32 = lutParams.y;

var sun = vec2<f32>(uniformBuffer.sunU, uniformBuffer.sunV);
var sun_direction: vec3<f32> = ComputeSphereNormal(vec2<f32>(sun.x, sun.y), 0.0, PI_2, 0.0, PI);

var SunDir: vec3<f32>;
{
var UpVector: vec3<f32> = WorldPos / viewHeight;
var sunZenithCosAngle: f32 = dot(UpVector, sun_direction);
SunDir = normalize(vec3<f32>(sqrt(1.0 - sunZenithCosAngle * sunZenithCosAngle), 0.0, sunZenithCosAngle));
}

WorldPos = vec3<f32>(0.0, 0.0, viewHeight);

var viewZenithSinAngle: f32 = sqrt(1.0 - viewZenithCosAngle * viewZenithCosAngle);
var WorldDir = vec3<f32>(
viewZenithSinAngle * lightViewCosAngle,
viewZenithSinAngle * sqrt(1.0 - lightViewCosAngle * lightViewCosAngle),
viewZenithCosAngle
);

// Move to top atmosphere
if !MoveToTopAtmosphere(&WorldPos, WorldDir, Atmosphere.TopRadius) {
// Ray is not intersecting the atmosphere
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}

const ground: bool = false;
const SampleCountIni: f32 = 30.0;
const DepthBufferValue: f32 = -1.0;
const VariableSampleCount: bool = true;
const MieRayPhase: bool = true;
var texSize = vec2<f32>(uniformBuffer.width, uniformBuffer.height);
var ss: SingleScatteringResult = IntegrateScatteredLuminance(
pixPos, WorldPos, WorldDir, SunDir, Atmosphere,
ground, SampleCountIni, DepthBufferValue, VariableSampleCount,
MieRayPhase, defaultTMaxMax, texSize
);

var L: vec3<f32> = ss.L;

return vec4<f32>(L, 1.0);
}
252 changes: 252 additions & 0 deletions packages/atmosphere/textures/AtmosphericScatteringSky.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import { AtmosphericScatteringSky_shader } from '../shader/AtmosphericScatteringSky_shader';
import {
Engine3D,
Vector3,
Color,
HDRTextureCube,
Texture,
ComputeShader,
UniformGPUBuffer,
VirtualTexture,
GPUTextureFormat,
GPUContext,
GPUAddressMode
} from "@orillusion/core";

/**
* AtmosphericScattering Sky Setting
* @group Texture
*/
export class AtmosphericScatteringSkySetting {
public sunRadius: number = 500.0;
public sunRadiance: number = 11.0;
public mieG: number = 0.76;
public mieHeight: number = 1200;
public eyePos: number = 1500;
public sunX: number = 0.71;
public sunY: number = 0.56;
public sunBrightness: number = 1.0;
public displaySun: boolean = true;
public enableClouds: boolean = true;
public showV1: boolean = false;
public defaultTextureCubeSize: number = 512;
public defaultTexture2DSize: number = 1024;
public skyColor: Color = new Color(1, 1, 1, 1);
public hdrExposure: number = 2;
}

/**
* Atmospheric Scattering Sky Texture
* @group Texture
*/
export class AtmosphericScatteringSky extends HDRTextureCube {
private _transmittanceLut: TransmittanceTexture2D;
private _multipleScatteringLut: MultipleScatteringTexture2D;
private _skyTexture: SkyTexture2D;
private _skyViewLut: SkyViewTexture2D;
private _cloudNoiseTexture: CloudNoiseTexture2D;
private _cubeSize: number;
public readonly setting: AtmosphericScatteringSkySetting;
private _internalTexture: AtmosphericTexture;

/**
* @constructor
* @param setting AtmosphericScatteringSkySetting
* @returns
*/
constructor(setting: AtmosphericScatteringSkySetting) {
super();
this.setting = setting;
this.isHDRTexture = true;
this._internalTexture = new AtmosphericTexture(setting.defaultTexture2DSize, setting.defaultTexture2DSize * 0.5);
this._internalTexture.updateUniforms(this.setting);
this._internalTexture.update();
this._internalTexture.isHDRTexture = true;
this._cubeSize = setting.defaultTextureCubeSize;
this._cloudNoiseTexture = new CloudNoiseTexture2D(64, 64);
this._cloudNoiseTexture.updateUniforms(this.setting);
this._cloudNoiseTexture.update();
this._transmittanceLut = new TransmittanceTexture2D(256, 64);
this._transmittanceLut.updateUniforms(this.setting);
this._transmittanceLut.update();
this._multipleScatteringLut = new MultipleScatteringTexture2D(32, 32);
this._multipleScatteringLut.updateUniforms(this.setting);
this._multipleScatteringLut.updateTransmittance(this._transmittanceLut, this._cloudNoiseTexture);
this._multipleScatteringLut.update();
this._skyViewLut = new SkyViewTexture2D(192, 108);
this._skyViewLut.updateUniforms(this.setting);
this._skyViewLut.updateTextures(this._transmittanceLut, this._multipleScatteringLut, this._cloudNoiseTexture);
this._skyViewLut.update();
this._skyTexture = new SkyTexture2D(setting.defaultTexture2DSize, setting.defaultTexture2DSize * 0.5);
this._skyTexture.updateUniforms(this.setting);
this._skyTexture.updateTextures(this._transmittanceLut, this._multipleScatteringLut, this._skyViewLut, this._cloudNoiseTexture);
this._skyTexture.isHDRTexture = true;
this._skyTexture.update();
this.createFromTexture(this._cubeSize, this._skyTexture);
return this;
}

public get texture2D(): Texture {
return this._skyTexture;
}

/**
* @internal
* @returns
*/
public apply(view?: any): this {
if (this.setting.showV1) {
this._internalTexture.updateUniforms(this.setting);
this._internalTexture.update();
this._faceData.uploadErpTexture(this._internalTexture);
} else {
this._skyTexture.updateUniforms(this.setting);
this._skyTexture.updateTextures(this._transmittanceLut, this._multipleScatteringLut, this._skyViewLut, this._cloudNoiseTexture);
this._skyTexture.update();
this._faceData.uploadErpTexture(this._skyTexture);
}
return this;
}
}

/**
* @internal
*/
class AtmosphericTexture extends VirtualTexture {
protected _computeShader: ComputeShader;
private _uniformBuffer: UniformGPUBuffer;
private _workerSize: Vector3 = new Vector3(8, 8, 1);

set workerSize(value: Vector3) {
this._workerSize = value;
}

get workerSize(): Vector3 {
return this._workerSize;
}

constructor(width: number, height: number, numberLayer: number = 1) {
super(width, height, GPUTextureFormat.rgba16float, false, GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING, numberLayer);
this._computeShader = new ComputeShader(AtmosphericScatteringSky_shader.cs);
this._computeShader.entryPoint = 'CsMain';
this.magFilter = 'linear';
this.minFilter = 'linear';
this.initCompute(width, height, numberLayer);
}

protected initCompute(w: number, h: number, d: number = 1): void {
this._uniformBuffer = new UniformGPUBuffer(16 * 4);
this._uniformBuffer.apply();

this._computeShader.setUniformBuffer('uniformBuffer', this._uniformBuffer);
this._computeShader.setStorageTexture(`outTexture`, this);
this._computeShader.setSamplerTexture(`transmittanceTexture`, Engine3D.res.blackTexture);
this._computeShader.setSamplerTexture(`multipleScatteringTexture`, Engine3D.res.blackTexture);
this._computeShader.setSamplerTexture(`cloudTexture`, Engine3D.res.blackTexture);
this._computeShader.workerSizeX = w / this._workerSize.x;
this._computeShader.workerSizeY = h / this._workerSize.y;
this._computeShader.workerSizeZ = this._workerSize.z;
}

public updateUniforms(setting: AtmosphericScatteringSkySetting): this {
this._uniformBuffer.setFloat('width', this.width);
this._uniformBuffer.setFloat('height', this.height);
this._uniformBuffer.setFloat('sunU', setting.sunX);
this._uniformBuffer.setFloat('sunV', setting.sunY);
this._uniformBuffer.setFloat('eyePos', setting.eyePos);
this._uniformBuffer.setFloat('sunRadius', setting.sunRadius);
this._uniformBuffer.setFloat('sunRadiance', setting.sunRadiance);
this._uniformBuffer.setFloat('mieG', setting.mieG);
this._uniformBuffer.setFloat('mieHeight', setting.mieHeight);
this._uniformBuffer.setFloat('sunBrightness', setting.sunBrightness);
this._uniformBuffer.setFloat('displaySun', setting.displaySun ? 1 : 0);
this._uniformBuffer.setFloat('enableClouds', setting.enableClouds ? 1 : 0);
this._uniformBuffer.setFloat('hdrExposure', setting.hdrExposure);
this._uniformBuffer.setColor('skyColor', setting.skyColor);
this._uniformBuffer.apply();
return this;
}

public update(): this {
let command = GPUContext.beginCommandEncoder();
GPUContext.computeCommand(command, [this._computeShader]);
GPUContext.endCommandEncoder(command);
return this;
}
}

/**
* @internal
*/
class TransmittanceTexture2D extends AtmosphericTexture {
constructor(width: number, height: number) {
super(width, height);
this._computeShader = new ComputeShader(AtmosphericScatteringSky_shader.transmittance_cs);
this.initCompute(width, height);
}
}

/**
* @internal
*/
class MultipleScatteringTexture2D extends AtmosphericTexture {
constructor(width: number, height: number) {
super(width, height);
this._computeShader = new ComputeShader(AtmosphericScatteringSky_shader.multiscatter_cs);
this.workerSize.set(1, 1, 64);
this.initCompute(width, height);
}

public updateTransmittance(transmittanceTexture: TransmittanceTexture2D, cloudTexture: CloudNoiseTexture2D) {
this._computeShader.setSamplerTexture(`transmittanceTexture`, transmittanceTexture);
this._computeShader.setSamplerTexture(`cloudTexture`, cloudTexture);
}
}

/**
* @internal
*/
class SkyViewTexture2D extends AtmosphericTexture {
constructor(width: number, height: number) {
super(width, height);
this._computeShader = new ComputeShader(AtmosphericScatteringSky_shader.skyview_cs);
this.initCompute(width, height);
}

public updateTextures(transmittanceTexture: TransmittanceTexture2D, multipleScatteringTexture: MultipleScatteringTexture2D, cloudTexture: CloudNoiseTexture2D) {
this._computeShader.setSamplerTexture(`transmittanceTexture`, transmittanceTexture);
this._computeShader.setSamplerTexture(`multipleScatteringTexture`, multipleScatteringTexture);
this._computeShader.setSamplerTexture(`cloudTexture`, cloudTexture);
}
}

/**
* @internal
*/
class SkyTexture2D extends AtmosphericTexture {
constructor(width: number, height: number) {
super(width, height);
this._computeShader = new ComputeShader(AtmosphericScatteringSky_shader.raymarch_cs);
this.initCompute(width, height);
}

public updateTextures(transmittanceTexture: TransmittanceTexture2D, multipleScatteringTexture: MultipleScatteringTexture2D, skyViewTexture: SkyViewTexture2D, cloudTexture: CloudNoiseTexture2D) {
this._computeShader.setSamplerTexture(`transmittanceTexture`, transmittanceTexture);
this._computeShader.setSamplerTexture(`multipleScatteringTexture`, multipleScatteringTexture);
this._computeShader.setSamplerTexture(`skyTexture`, skyViewTexture);
this._computeShader.setSamplerTexture(`cloudTexture`, cloudTexture);
}
}

/**
* @internal
*/
class CloudNoiseTexture2D extends AtmosphericTexture {
constructor(width: number, height: number) {
super(width, height);
this._computeShader = new ComputeShader(AtmosphericScatteringSky_shader.cloud_cs);
this.addressModeU = GPUAddressMode.repeat;
this.addressModeV = GPUAddressMode.repeat;
this.initCompute(width, height);
}
}
30 changes: 30 additions & 0 deletions packages/atmosphere/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"sourceMap": false,
"resolveJsonModule": true,
"esModuleInterop": true,
"types": ["vite/client", "@webgpu/types"],
// for build
// "noEmit": true,
"declaration": true,
"declarationDir": "dist",
"outDir": "dist",
"skipLibCheck": true,
"noImplicitAny": false,
"noUnusedLocals": false,
"noUnusedParameters":false,
"noImplicitReturns": false,
"strictPropertyInitialization": false,
"strictNullChecks": false,
"strict": false,
"paths": {
"@orillusion/core": ["../../src"],
"@orillusion/*": ["../*"]
}
}
}
26 changes: 26 additions & 0 deletions packages/atmosphere/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { defineConfig } from 'vite'
const path = require('path')
export default defineConfig({
resolve: {
alias: {
'@orillusion/core': path.resolve(__dirname, '../../src'),
'@orillusion': path.resolve(__dirname, '../')
}
},
build: {
target: 'esnext',
lib: {
entry: path.resolve('index.ts'),
name: 'Atmosphere',
fileName: (format) => `atmosphere.${format}.js`
},
rollupOptions: {
external: ['@orillusion/core'],
output: {
globals: {
'@orillusion/core': 'Orillusion'
}
}
}
}
})
130 changes: 130 additions & 0 deletions samples/ext/Sample_AtmosphericSky.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { GUIHelp } from "@orillusion/debug/GUIHelp";
import { createExampleScene } from "@samples/utils/ExampleScene";
import { Engine3D, GPUCullMode, MeshRenderer, Object3D, PlaneGeometry, Scene3D, UnLitMaterial, Vector3 } from "@orillusion/core";
import { AtmosphericComponent } from "@orillusion/atmosphere";

// sample of AtmosphericSky
class Sample_AtmosphericSky {
async run() {
// init engine
await Engine3D.init({});
// init scene
let example = createExampleScene();
let scene: Scene3D = example.scene;
let camera = example.camera;
camera.fov = 90;
// start renderer
Engine3D.startRenderView(scene.view);
// add atmospheric sky
let sky = scene.addComponent(AtmosphericComponent);
sky.sunX = 0.25;
let y = 100;
const settings = {
displayTextures: true,
}
let objs: Object3D[] = [];
{
let texture = sky['_atmosphericScatteringSky']['_transmittanceLut'];
let ulitMaterial = new UnLitMaterial();
ulitMaterial.baseMap = texture;
ulitMaterial.cullMode = GPUCullMode.none;
let obj = new Object3D();
let r = obj.addComponent(MeshRenderer);
r.material = ulitMaterial;
r.geometry = new PlaneGeometry(50, 25, 1, 1, Vector3.Z_AXIS);
obj.y = y;
y -= 25;
scene.addChild(obj);
objs.push(obj);
}
{
let texture = sky['_atmosphericScatteringSky']['_multipleScatteringLut'];
let ulitMaterial = new UnLitMaterial();
ulitMaterial.baseMap = texture;
ulitMaterial.cullMode = GPUCullMode.none;
let obj = new Object3D();
let r = obj.addComponent(MeshRenderer);
r.material = ulitMaterial;
r.geometry = new PlaneGeometry(25, 25, 1, 1, Vector3.Z_AXIS);
obj.y = y;
y -= 25;
scene.addChild(obj);
objs.push(obj);
}
{
let texture = sky['_atmosphericScatteringSky']['_skyViewLut'];
let ulitMaterial = new UnLitMaterial();
ulitMaterial.baseMap = texture;
ulitMaterial.cullMode = GPUCullMode.none;
let obj = new Object3D();
let r = obj.addComponent(MeshRenderer);
r.material = ulitMaterial;
r.geometry = new PlaneGeometry(50, 25, 1, 1, Vector3.Z_AXIS);
obj.y = y;
y -= 25;
scene.addChild(obj);
objs.push(obj);
}
{
// _cloudNoiseTexture
let texture = sky['_atmosphericScatteringSky']['_cloudNoiseTexture'];
let ulitMaterial = new UnLitMaterial();
ulitMaterial.baseMap = texture;
ulitMaterial.cullMode = GPUCullMode.none;
let obj = new Object3D();
let r = obj.addComponent(MeshRenderer);
r.material = ulitMaterial;
r.geometry = new PlaneGeometry(25, 25, 1, 1, Vector3.Z_AXIS);
obj.y = y;
y -= 25;
scene.addChild(obj);
objs.push(obj);
}

{
let texture = sky['_atmosphericScatteringSky']['_skyTexture'];
let ulitMaterial = new UnLitMaterial();
ulitMaterial.baseMap = texture;
ulitMaterial.cullMode = GPUCullMode.none;
let obj = new Object3D();
let r = obj.addComponent(MeshRenderer);
r.material = ulitMaterial;
r.geometry = new PlaneGeometry(50, 25, 1, 1, Vector3.Z_AXIS);
obj.y = y;
y -= 25;
scene.addChild(obj);
objs.push(obj);
}


// gui
GUIHelp.init();
const name = 'AtmosphericSky';
GUIHelp.addFolder(name);
GUIHelp.add(sky, 'sunX', 0, 1, 0.01);
GUIHelp.add(sky, 'sunY', 0.4, 1.6, 0.01);
GUIHelp.add(sky, 'eyePos', 0, 7000, 1);
GUIHelp.add(sky, 'sunRadius', 0, 1000, 0.01);
GUIHelp.add(sky, 'sunRadiance', 0, 100, 0.01);
GUIHelp.add(sky, 'sunBrightness', 0, 10, 0.01);
GUIHelp.add(sky, 'hdrExposure', 0, 5, 0.01);
GUIHelp.add(sky, 'displaySun', 0, 1, 0.01);
GUIHelp.add(sky, 'enable');
// bool whether to display the textures
GUIHelp.add(settings, 'displayTextures').onChange((v) => {
objs.forEach(obj => {
obj.transform.enable = v;
});
});
GUIHelp.open();
GUIHelp.endFolder();

// add folder for camera
GUIHelp.addFolder('Camera');
GUIHelp.add(camera, 'fov', 1, 180, 1);
GUIHelp.open();
GUIHelp.endFolder();
}
}

new Sample_AtmosphericSky().run();