Skip to content

Commit

Permalink
Merge pull request #346 from RagnarokResearchLab/133-directional-rsw-…
Browse files Browse the repository at this point in the history
…lighting

Implement directional lighting with screen blending for RSW scenes
  • Loading branch information
rdw-software authored Jan 29, 2024
2 parents ed70777 + 21473bc commit 67d2736
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 18 deletions.
13 changes: 11 additions & 2 deletions Core/FileFormats/RagnarokMap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,21 @@ function RagnarokMap:Construct(mapID, fileSystem)

local ambient = {
red = self.rsw.ambientLight.diffuseColor.red,
green = self.rsw.ambientLight.diffuseColor.red,
blue = self.rsw.ambientLight.diffuseColor.red,
green = self.rsw.ambientLight.diffuseColor.green,
blue = self.rsw.ambientLight.diffuseColor.blue,
intensity = 1,
}
scene.ambientLight = ambient

local sun = {
red = self.rsw.directionalLight.diffuseColor.red,
green = self.rsw.directionalLight.diffuseColor.green,
blue = self.rsw.directionalLight.diffuseColor.blue,
intensity = 1,
rayDirection = self.rsw.directionalLight.direction,
}
scene.directionalLight = sun

printf("[RagnarokMap] Entering world %s (%s)", mapID, scene.displayName)

return scene
Expand Down
21 changes: 21 additions & 0 deletions Core/FileFormats/RagnarokRSW.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ local BinaryReader = require("Core.FileFormats.BinaryReader")
local RagnarokGND = require("Core.FileFormats.RagnarokGND")
local QuadTreeRange = require("Core.FileFormats.RSW.QuadTreeRange")

local Matrix3D = require("Core.VectorMath.Matrix3D")
local Vector3D = require("Core.VectorMath.Vector3D")

local RagnarokRSW = {
SCENE_OBJECT_TYPE_ANIMATED_PROP = 1,
SCENE_OBJECT_TYPE_DYNAMIC_LIGHT_SOURCE = 2,
Expand Down Expand Up @@ -221,6 +224,10 @@ function RagnarokRSW:DecodeEnvironmentalLightSources()
},
}

local rayDirection =
self:ComputeSunRayDirection(directionalLight.latitudeInDegrees, directionalLight.longitudeInDegrees)
directionalLight.direction = rayDirection

local ambientLight = {
diffuseColor = {
red = tonumber(environmentalLightInfo.ambient_color.red),
Expand All @@ -245,6 +252,20 @@ function RagnarokRSW:DecodeEnvironmentalLightSources()
self.prebakedShadowmapAlpha = tonumber(environmentalLightInfo.shadowmap_alpha)
end

function RagnarokRSW:ComputeSunRayDirection(latitudeInDegrees, longitudeInDegrees)
local sunRayDirection = Vector3D(0, 1, 0)

local rotationAroundX = Matrix3D:CreateAxisRotationX(latitudeInDegrees)
local rotationAroundY = Matrix3D:CreateAxisRotationY(longitudeInDegrees)

sunRayDirection:Transform(rotationAroundX)
sunRayDirection:Transform(rotationAroundY)

sunRayDirection.y = -1 * sunRayDirection.y

return sunRayDirection
end

function RagnarokRSW:DecodeMapBoundaries()
local outerWorldBoundingBox = self.reader:GetTypedArray("rsw_bounding_box_t")

Expand Down
39 changes: 39 additions & 0 deletions Core/NativeClient/Renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ local table_insert = table.insert

local Color = require("Core.NativeClient.DebugDraw.Color")
local DEFAULT_AMBIENT_COLOR = { red = 1, green = 1, blue = 1, intensity = 1 }
local DEFAULT_SUNLIGHT_COLOR = DEFAULT_AMBIENT_COLOR
local DEFAULT_SUNLIGHT_DIRECTION = { x = 1, y = -1, z = 1 }

local Renderer = {
clearColorRGBA = { Color.GREY.red, Color.GREY.green, Color.GREY.blue, 0 },
Expand All @@ -50,6 +52,17 @@ local Renderer = {
blue = DEFAULT_AMBIENT_COLOR.blue,
intensity = DEFAULT_AMBIENT_COLOR.intensity,
},
directionalLight = {
red = DEFAULT_SUNLIGHT_COLOR.red,
green = DEFAULT_SUNLIGHT_COLOR.green,
blue = DEFAULT_SUNLIGHT_COLOR.blue,
intensity = DEFAULT_SUNLIGHT_COLOR.intensity,
rayDirection = {
x = DEFAULT_SUNLIGHT_DIRECTION.x,
y = DEFAULT_SUNLIGHT_DIRECTION.y,
z = DEFAULT_SUNLIGHT_DIRECTION.z,
},
},
DEBUG_DISCARDED_BACKGROUND_PIXELS = false, -- This is really slow (disk I/O); don't enable unless necessary
numWidgetTransformsUsedThisFrame = 0,
errorStrings = {
Expand Down Expand Up @@ -677,6 +690,14 @@ function Renderer:UpdateScenewideUniformBuffer(deltaTime)
perSceneUniformData.ambientLightBlue = self.ambientLight.blue
perSceneUniformData.ambientLightIntensity = self.ambientLight.intensity
perSceneUniformData.deltaTime = deltaTime
perSceneUniformData.directionalLightDirectionX = self.directionalLight.rayDirection.x
perSceneUniformData.directionalLightDirectionY = self.directionalLight.rayDirection.y
perSceneUniformData.directionalLightDirectionZ = self.directionalLight.rayDirection.z
perSceneUniformData.directionalLightRed = self.directionalLight.red
perSceneUniformData.directionalLightGreen = self.directionalLight.green
perSceneUniformData.directionalLightBlue = self.directionalLight.blue
perSceneUniformData.directionalLightIntensity = self.directionalLight.intensity
assert(self.directionalLight.intensity == 1, "The directional light must always be at full intensity")

Queue:WriteBuffer(
Device:GetQueue(self.wgpuDevice),
Expand Down Expand Up @@ -840,6 +861,16 @@ function Renderer:LoadSceneObjects(scene)
self.ambientLight.blue = DEFAULT_AMBIENT_COLOR.blue
self.ambientLight.intensity = DEFAULT_AMBIENT_COLOR.intensity
end

if scene.directionalLight then
self.directionalLight = scene.directionalLight
else
self.directionalLight.red = DEFAULT_SUNLIGHT_COLOR.red
self.directionalLight.green = DEFAULT_SUNLIGHT_COLOR.green
self.directionalLight.blue = DEFAULT_SUNLIGHT_COLOR.blue
self.directionalLight.intensity = DEFAULT_SUNLIGHT_COLOR.intensity
self.directionalLight.rayDirection = DEFAULT_SUNLIGHT_DIRECTION
end
end

function Renderer:DebugDumpTextures(mesh, fileName)
Expand Down Expand Up @@ -876,6 +907,14 @@ function Renderer:ResetScene()
self.ambientLight.green = DEFAULT_AMBIENT_COLOR.green
self.ambientLight.blue = DEFAULT_AMBIENT_COLOR.blue
self.ambientLight.intensity = DEFAULT_AMBIENT_COLOR.intensity

self.directionalLight.red = DEFAULT_SUNLIGHT_COLOR.red
self.directionalLight.green = DEFAULT_SUNLIGHT_COLOR.green
self.directionalLight.blue = DEFAULT_SUNLIGHT_COLOR.blue
self.directionalLight.intensity = DEFAULT_SUNLIGHT_COLOR.intensity
self.directionalLight.rayDirection.x = DEFAULT_SUNLIGHT_DIRECTION.x
self.directionalLight.rayDirection.y = DEFAULT_SUNLIGHT_DIRECTION.y
self.directionalLight.rayDirection.z = DEFAULT_SUNLIGHT_DIRECTION.z
end

return Renderer
3 changes: 3 additions & 0 deletions Core/NativeClient/WebGPU/Shaders/BasicTriangleShader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ struct PerSceneData {
viewportWidth: f32,
viewportHeight: f32,
deltaTime: f32,
unusedPadding: f32,
directionalLightDirection: vec4f,
directionalLightColor: vec4f,
};

@group(0) @binding(0) var<uniform> uPerSceneData: PerSceneData;
Expand Down
31 changes: 27 additions & 4 deletions Core/NativeClient/WebGPU/Shaders/TerrainGeometryShader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ struct VertexInput {
@location(0) position: vec3f,
@location(1) color: vec3f,
@location(2) diffuseTextureCoords: vec2f,
@location(3) surfaceNormal: vec3f,
};

struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) color: vec3f,
@location(1) diffuseTextureCoords: vec2f,
@location(2) surfaceNormal: vec3f,
};

// CameraBindGroup: Updated once per frame
Expand All @@ -18,6 +20,9 @@ struct PerSceneData {
viewportWidth: f32,
viewportHeight: f32,
deltaTime: f32,
unusedPadding: f32,
directionalLightDirection: vec4f,
directionalLightColor: vec4f,
};

@group(0) @binding(0) var<uniform> uPerSceneData: PerSceneData;
Expand All @@ -40,6 +45,12 @@ var<uniform> uMaterialInstanceData: PerMaterialData;

const MATH_PI = 3.14159266;
const DEBUG_ALPHA_OFFSET = 0.0; // Set to non-zero value (e.g., 0.2) to make transparent background pixels visible
const ZERO_VECTOR = vec3f(0.0, 0.0, 0.0);
const UNIT_VECTOR = vec3f(1.0, 1.0, 1.0);

fn clampToUnitRange(color : vec3f) -> vec3f {
return clamp(color, ZERO_VECTOR, UNIT_VECTOR);
}

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
Expand Down Expand Up @@ -102,6 +113,7 @@ fn vs_main(in: VertexInput) -> VertexOutput {
out.position = projectionMatrix * viewMatrix * T1 * S * homogeneousPosition;

out.color = in.color;
out.surfaceNormal = in.surfaceNormal;
out.diffuseTextureCoords = in.diffuseTextureCoords;
return out;
}
Expand All @@ -110,12 +122,23 @@ fn vs_main(in: VertexInput) -> VertexOutput {
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let textureCoords = in.diffuseTextureCoords;
let diffuseTextureColor = textureSample(diffuseTexture, diffuseTextureSampler, textureCoords);
let materialColor = vec4f(uMaterialInstanceData.diffuseRed, uMaterialInstanceData.diffuseGreen, uMaterialInstanceData.diffuseBlue, uMaterialInstanceData.materialOpacity);
let finalColor = in.color * diffuseTextureColor.rgb * uPerSceneData.ambientLight.rgb * materialColor.rgb;
let normal = normalize(in.surfaceNormal);
let sunlightColor = uPerSceneData.directionalLightColor.rgb;
let ambientColor = uPerSceneData.ambientLight.rgb;

// Simulated fixed-function pipeline (DirectX7/9) - no specular highlights needed AFAICT?
let sunlightRayOrigin = -normalize(uPerSceneData.directionalLightDirection.xyz);
let sunlightColorContribution = max(dot(sunlightRayOrigin, normal), 0.0);
let directionalLightColor = sunlightColorContribution * sunlightColor;
let combinedLightContribution = clampToUnitRange(directionalLightColor + ambientColor);

// Screen blending increases the vibrancy of colors (see https://en.wikipedia.org/wiki/Blend_modes#Screen)
let contrastCorrectionColor = clampToUnitRange(ambientColor + sunlightColor - (sunlightColor * ambientColor));
let fragmentColor = in.color * contrastCorrectionColor * combinedLightContribution * diffuseTextureColor.rgb ;

// Gamma-correction:
// WebGPU assumes that the colors output by the fragment shader are given in linear space
// When setting the surface format to BGRA8UnormSrgb it performs a linear to sRGB conversion
let gammaCorrectedColor = pow(finalColor.rgb, vec3f(2.2));
return vec4f(gammaCorrectedColor, diffuseTextureColor.a * materialColor.a + DEBUG_ALPHA_OFFSET);
let gammaCorrectedColor = pow(fragmentColor.rgb, vec3f(2.2));
return vec4f(gammaCorrectedColor, diffuseTextureColor.a + DEBUG_ALPHA_OFFSET);
}
3 changes: 3 additions & 0 deletions Core/NativeClient/WebGPU/Shaders/UserInterfaceShader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ struct PerSceneData {
viewportWidth: f32,
viewportHeight: f32,
deltaTime: f32,
unusedPadding: f32,
directionalLightDirection: vec4f,
directionalLightColor: vec4f,
};

@group(0) @binding(0)
Expand Down
3 changes: 3 additions & 0 deletions Core/NativeClient/WebGPU/Shaders/WaterSurfaceShader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ struct PerSceneData {
viewportWidth: f32,
viewportHeight: f32,
deltaTime: f32,
unusedPadding: f32,
directionalLightDirection: vec4f,
directionalLightColor: vec4f,
};

@group(0) @binding(0) var<uniform> uPerSceneData: PerSceneData;
Expand Down
10 changes: 9 additions & 1 deletion Core/NativeClient/WebGPU/UniformBuffer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,16 @@ local UniformBuffer = {
float viewportWidth; // 148
float viewportHeight; // 152
float deltaTime; // 156
float padding[1]; // 160 - Aligned block must end here or colors are messed up
float directionalLightDirectionX; // 164
float directionalLightDirectionY; // 168
float directionalLightDirectionZ; // 172
float directionalLightDirectionW; // 176
float directionalLightRed; // 180
float directionalLightGreen; // 184
float directionalLightBlue; // 188
float directionalLightIntensity; // 192
// Padding needs to be updated whenever the struct changes!
float padding; // 160
} scenewide_uniform_t;
typedef struct PerMaterialData {
float materialOpacity; // 4
Expand Down
14 changes: 11 additions & 3 deletions Tests/FileFormats/RagnarokMap.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,18 @@ describe("RagnarokMap", function()
assertEquals(map.meshes[3].triangleConnections, waterPlanes[1].surfaceGeometry.triangleConnections)
assertEquals(map.meshes[3].diffuseTextureCoords, waterPlanes[1].surfaceGeometry.diffuseTextureCoords)

assertEqualNumbers(map.ambientLight.red, 0.80000001192093, 1E-3)
assertEqualNumbers(map.ambientLight.green, 0.80000001192093, 1E-3)
assertEqualNumbers(map.ambientLight.blue, 0.80000001192093, 1E-3)
assertEqualNumbers(map.ambientLight.red, 0.25, 1E-3)
assertEqualNumbers(map.ambientLight.green, 0.55, 1E-3)
assertEqualNumbers(map.ambientLight.blue, 0.77, 1E-3)
assertEqualNumbers(map.ambientLight.intensity, 1, 1E-3)

assertEqualNumbers(map.directionalLight.red, 13, 1E-3)
assertEqualNumbers(map.directionalLight.green, 14, 1E-3)
assertEqualNumbers(map.directionalLight.blue, 15, 1E-3)
assertEqualNumbers(map.directionalLight.intensity, 1, 1E-3)
assertEqualNumbers(map.directionalLight.rayDirection.x, 0.49999997019768, 1E-3)
assertEqualNumbers(map.directionalLight.rayDirection.y, -0.70710676908493, 1E-3)
assertEqualNumbers(map.directionalLight.rayDirection.z, 0.49999997019768, 1E-3)
end)

it("should set a default display name if the given map ID wasn't assigned one in the DB", function()
Expand Down
75 changes: 75 additions & 0 deletions Tests/FileFormats/RagnarokRSW.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -920,4 +920,79 @@ describe("RagnarokRSW", function()
assertEquals(rsw.dynamicLightSources[1].normalizedWorldPosition.y, 29.8)
end)
end)

describe("ComputeSunRayDirection", function()
it("should return the -Y axis if the origin of the spherical coordinate system was passed", function()
local direction = RagnarokRSW:ComputeSunRayDirection(0, 0)
assertEquals(direction.x, 0)
assertEquals(direction.y, -1)
assertEquals(direction.z, 0)
end)

it("should rotate the sun around the X axis if a non-zero latitude was passed", function()
local direction = RagnarokRSW:ComputeSunRayDirection(45, 0)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, -0.70710676908493, 1E-3)
assertEqualNumbers(direction.z, 0.70710676908493, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(90, 0)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, 0, 1E-3)
assertEqualNumbers(direction.z, 1, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(-45, 0)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, -0.70710676908493, 1E-3)
assertEqualNumbers(direction.z, -0.70710676908493, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(-90, 0)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, 0, 1E-3)
assertEqualNumbers(direction.z, -1, 1E-3)
end)

it("should rotate the sun around the Y axis if a non-zero longitude was passed", function()
local direction = RagnarokRSW:ComputeSunRayDirection(0, 45)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, -1, 1E-3)
assertEqualNumbers(direction.z, 0, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(0, 90)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, -1, 1E-3)
assertEqualNumbers(direction.z, 0, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(0, -45)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, -1, 1E-3)
assertEqualNumbers(direction.z, 0, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(0, -90)
assertEqualNumbers(direction.x, 0, 1E-3)
assertEqualNumbers(direction.y, -1, 1E-3)
assertEqualNumbers(direction.z, 0, 1E-3)
end)

it("should rotate the sun around the X and then the Y axis", function()
local direction = RagnarokRSW:ComputeSunRayDirection(45, 45)
assertEqualNumbers(direction.x, 0.49999997019768, 1E-3)
assertEqualNumbers(direction.y, -0.70710676908493, 1E-3)
assertEqualNumbers(direction.z, 0.49999997019768, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(90, 90)
assertEqualNumbers(direction.x, 1, 1E-3)
assertEqualNumbers(direction.y, 0, 1E-3)
assertEqualNumbers(direction.z, 0, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(-45, -45)
assertEqualNumbers(direction.x, 0.49999997019768, 1E-3)
assertEqualNumbers(direction.y, -0.70710676908493, 1E-3)
assertEqualNumbers(direction.z, -0.49999997019768, 1E-3)

direction = RagnarokRSW:ComputeSunRayDirection(-90, -90)
assertEqualNumbers(direction.x, 1, 1E-3)
assertEqualNumbers(direction.y, 0, 1E-3)
assertEqualNumbers(direction.z, 0, 1E-3)
end)
end)
end)
Binary file modified Tests/Fixtures/Borftopia/borftopia.rsw
Binary file not shown.
Loading

0 comments on commit 67d2736

Please sign in to comment.