Shadows.
This commit is contained in:
parent
0060116835
commit
3fdee3d9b6
11 changed files with 3926 additions and 1723 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -22,3 +22,4 @@ docs/.asciidoctor/
|
||||||
/Menu.sh
|
/Menu.sh
|
||||||
/Menu.bat
|
/Menu.bat
|
||||||
/data/
|
/data/
|
||||||
|
screenshots/
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ API Changes
|
||||||
- 3D scenes. A game can draw a 3D scene between the disc video and the
|
- 3D scenes. A game can draw a 3D scene between the disc video and the
|
||||||
overlay: primitive meshes and script-built geometry, materials with
|
overlay: primitive meshes and script-built geometry, materials with
|
||||||
colour, textures from sprites, the disc or a loaded video, metallic and
|
colour, textures from sprites, the disc or a loaded video, metallic and
|
||||||
roughness, up to eight lights, any node as the camera, and glTF 2.0
|
roughness, up to eight lights each able to cast shadows (cube maps for
|
||||||
|
point lights), any node as the camera, and glTF 2.0
|
||||||
models (.glb, self-contained) with node animation and skinning, placed
|
models (.glb, self-contained) with node animation and skinning, placed
|
||||||
any number of times. Everything is a node in one tree. New calls:
|
any number of times. Everything is a node in one tree. New calls:
|
||||||
scene*, node*, mesh*, material*, light*, camera*, model*, animation*,
|
scene*, node*, mesh*, material*, light*, camera*, model*, animation*,
|
||||||
|
|
|
||||||
|
|
@ -759,7 +759,10 @@ Up to eight lights shine at once: `LIGHT_DIRECTIONAL` (a sun, position
|
||||||
irrelevant), `LIGHT_POINT` (a bulb, fading with distance, optionally out to
|
irrelevant), `LIGHT_POINT` (a bulb, fading with distance, optionally out to
|
||||||
a range), and `LIGHT_SPOT` (a cone with inner and outer angles). Lights are
|
a range), and `LIGHT_SPOT` (a cone with inner and outer angles). Lights are
|
||||||
nodes, so they parent and animate like anything else. `sceneSetAmbient`
|
nodes, so they parent and animate like anything else. `sceneSetAmbient`
|
||||||
lights everything a little from everywhere. Any node can be the camera
|
lights everything a little from everywhere. Any light can cast soft-edged
|
||||||
|
shadows from everything opaque (`lightSetShadow`): directional and spot
|
||||||
|
lights through a shadow map, point lights through a cube map that reaches
|
||||||
|
every direction; `sceneSetShadowSize` trades sharpness for speed. Any node can be the camera
|
||||||
(`cameraSet`); without one, the scene is viewed from `(0, 0, 5)` looking at
|
(`cameraSet`); without one, the scene is viewed from `(0, 0, 5)` looking at
|
||||||
the origin. Perspective is the default; `cameraSetOrthographic` is there
|
the origin. Perspective is the default; `cameraSetOrthographic` is there
|
||||||
for diagrams and HUD-like scenes.
|
for diagrams and HUD-like scenes.
|
||||||
|
|
@ -2143,6 +2146,31 @@ How far a point or spot light reaches before fading to nothing, in world units;
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<lightsetintensity,lightSetIntensity>>
|
*See also:* <<lightsetintensity,lightSetIntensity>>
|
||||||
|
|
||||||
|
[#lightsetshadow]
|
||||||
|
==== lightSetShadow
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
lightSetShadow(node, shadow)
|
||||||
|
----
|
||||||
|
|
||||||
|
Makes the light cast shadows from everything opaque in the scene, or stops it. Any of the eight lights may cast, in any mix. A directional or spot light gets one shadow map; a point light gets a cube map, six views, which covers every direction (a bulb inside a room) at six times the cost.
|
||||||
|
|
||||||
|
*Notes:* Each shadow map costs one extra pass over the scene per frame, and each cube map six; the maps' size is set by `sceneSetShadowSize`. A modern integrated GPU manages eight casters comfortably; a Raspberry Pi wants one or two at `512`.
|
||||||
|
|
||||||
|
*Since:* 3.00.
|
||||||
|
*See also:* <<scenesetshadowsize,sceneSetShadowSize>>, <<lightnew,lightNew>>
|
||||||
|
|
||||||
|
.Example
|
||||||
|
[source,lua]
|
||||||
|
----
|
||||||
|
-- The sun casts shadows; the lamps only light.
|
||||||
|
sun = lightNew(LIGHT_DIRECTIONAL)
|
||||||
|
nodeSetPosition(sun, 3, 6, 4)
|
||||||
|
nodeLookAt(sun, 0, 0, 0)
|
||||||
|
lightSetShadow(sun, true)
|
||||||
|
----
|
||||||
|
|
||||||
[#material]
|
[#material]
|
||||||
=== Material
|
=== Material
|
||||||
|
|
||||||
|
|
@ -3374,6 +3402,19 @@ The colour the layer clears to each frame. `a` below `255` lets the video show t
|
||||||
*Since:* 3.00.
|
*Since:* 3.00.
|
||||||
*See also:* <<sceneenable,sceneEnable>>
|
*See also:* <<sceneenable,sceneEnable>>
|
||||||
|
|
||||||
|
[#scenesetshadowsize]
|
||||||
|
==== sceneSetShadowSize
|
||||||
|
|
||||||
|
[source,text]
|
||||||
|
----
|
||||||
|
sceneSetShadowSize(size)
|
||||||
|
----
|
||||||
|
|
||||||
|
Texels per side of every shadow map and cube face, `256` to `4096` (default `1024`). Larger is sharper and slower; `512` is a fair choice on a Raspberry Pi.
|
||||||
|
|
||||||
|
*Since:* 3.00.
|
||||||
|
*See also:* <<lightsetshadow,lightSetShadow>>, <<scenesetantialias,sceneSetAntialias>>
|
||||||
|
|
||||||
[#script]
|
[#script]
|
||||||
=== Script
|
=== Script
|
||||||
|
|
||||||
|
|
|
||||||
16
src/math3d.c
16
src/math3d.c
|
|
@ -205,6 +205,22 @@ Mat4T mat4Orthographic(float width, float height, float near, float far) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// An off-centre parallel projection (a shadow map fitted to what it covers).
|
||||||
|
Mat4T mat4OrthographicBounds(float left, float right, float bottom, float top, float near, float far) {
|
||||||
|
Mat4T out;
|
||||||
|
|
||||||
|
memset(&out, 0, sizeof(out));
|
||||||
|
out.m[0] = 2.0f / (right - left);
|
||||||
|
out.m[5] = 2.0f / (top - bottom);
|
||||||
|
out.m[10] = -1.0f / (far - near);
|
||||||
|
out.m[12] = -(right + left) / (right - left);
|
||||||
|
out.m[13] = -(top + bottom) / (top - bottom);
|
||||||
|
out.m[14] = -near / (far - near);
|
||||||
|
out.m[15] = 1.0f;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Mat4T mat4Perspective(float fovDegrees, float aspect, float near, float far) {
|
Mat4T mat4Perspective(float fovDegrees, float aspect, float near, float far) {
|
||||||
Mat4T out;
|
Mat4T out;
|
||||||
float f = 1.0f / tanf(DEGREES_TO_RADIANS(fovDegrees) / 2.0f);
|
float f = 1.0f / tanf(DEGREES_TO_RADIANS(fovDegrees) / 2.0f);
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ bool mat4Invert(Mat4T a, Mat4T *out);
|
||||||
Mat4T mat4LookAt(Vec3T eye, Vec3T target, Vec3T up);
|
Mat4T mat4LookAt(Vec3T eye, Vec3T target, Vec3T up);
|
||||||
Mat4T mat4Multiply(Mat4T a, Mat4T b);
|
Mat4T mat4Multiply(Mat4T a, Mat4T b);
|
||||||
Mat4T mat4Orthographic(float width, float height, float near, float far);
|
Mat4T mat4Orthographic(float width, float height, float near, float far);
|
||||||
|
Mat4T mat4OrthographicBounds(float left, float right, float bottom, float top, float near, float far);
|
||||||
Mat4T mat4Perspective(float fovDegrees, float aspect, float near, float far);
|
Mat4T mat4Perspective(float fovDegrees, float aspect, float near, float far);
|
||||||
Vec3T mat4TransformPoint(Mat4T a, Vec3T p);
|
Vec3T mat4TransformPoint(Mat4T a, Vec3T p);
|
||||||
Vec3T mat4TransformVector(Mat4T a, Vec3T v);
|
Vec3T mat4TransformVector(Mat4T a, Vec3T v);
|
||||||
|
|
|
||||||
580
src/scene.c
580
src/scene.c
|
|
@ -52,6 +52,18 @@
|
||||||
#define MIN_SEGMENTS 3
|
#define MIN_SEGMENTS 3
|
||||||
#define PI 3.14159265358979323846f
|
#define PI 3.14159265358979323846f
|
||||||
#define NO_HANDLE -1
|
#define NO_HANDLE -1
|
||||||
|
#define SHADOW_PIPELINES 4 // skinned x double sided
|
||||||
|
#define MAX_SHADOWS MAX_LIGHTS // Every light may cast; the arrays are sized to the lights in use
|
||||||
|
#define CUBE_FACES 6
|
||||||
|
#define SHADOW_NONE 0
|
||||||
|
#define SHADOW_MAP 1
|
||||||
|
#define SHADOW_CUBE 2
|
||||||
|
#define SHADOW_SIZE 1024
|
||||||
|
#define SHADOW_SIZE_MIN 256
|
||||||
|
#define SHADOW_SIZE_MAX 4096
|
||||||
|
#define SHADOW_BIAS 0.0015f
|
||||||
|
#define SHADOW_MARGIN 1.05f // The fitted light frustum, a little larger than the scene
|
||||||
|
#define SKIN_BOUNDS_GROW 1.5f // A skinned mesh moves beyond its bind pose
|
||||||
|
|
||||||
|
|
||||||
// Matches DrawUniforms in scene.hlsl.
|
// Matches DrawUniforms in scene.hlsl.
|
||||||
|
|
@ -76,13 +88,29 @@ typedef struct FragmentUniformsS {
|
||||||
float emissive[4];
|
float emissive[4];
|
||||||
float material[4];
|
float material[4];
|
||||||
float counts[4];
|
float counts[4];
|
||||||
|
float shadowParams[4];
|
||||||
|
Mat4T shadowMatrix[MAX_SHADOWS];
|
||||||
|
float shadowInfo[MAX_SHADOWS][4];
|
||||||
LightUniformT lights[MAX_LIGHTS];
|
LightUniformT lights[MAX_LIGHTS];
|
||||||
} FragmentUniformsT;
|
} FragmentUniformsT;
|
||||||
|
|
||||||
|
// One light's shadow for this frame.
|
||||||
|
typedef struct ShadowS {
|
||||||
|
int32_t node;
|
||||||
|
int32_t type; // SHADOW_MAP or SHADOW_CUBE
|
||||||
|
int32_t layer; // First layer in the shadow array (a point light uses six)
|
||||||
|
Mat4T matrix; // Map: the light's view-projection
|
||||||
|
Mat4T faces[CUBE_FACES]; // Cube: one per face
|
||||||
|
float near;
|
||||||
|
float far;
|
||||||
|
} ShadowT;
|
||||||
|
|
||||||
typedef struct MeshS {
|
typedef struct MeshS {
|
||||||
SDL_GPUBuffer *vertexBuffer;
|
SDL_GPUBuffer *vertexBuffer;
|
||||||
SDL_GPUBuffer *indexBuffer;
|
SDL_GPUBuffer *indexBuffer;
|
||||||
uint32_t indexCount;
|
uint32_t indexCount;
|
||||||
|
Vec3T boundsMin; // Of the vertices, for fitting the shadow map
|
||||||
|
Vec3T boundsMax;
|
||||||
bool skinned;
|
bool skinned;
|
||||||
bool used;
|
bool used;
|
||||||
} MeshT;
|
} MeshT;
|
||||||
|
|
@ -130,6 +158,7 @@ typedef struct NodeS {
|
||||||
int32_t mesh;
|
int32_t mesh;
|
||||||
int32_t material;
|
int32_t material;
|
||||||
LightT light;
|
LightT light;
|
||||||
|
bool castsShadow;
|
||||||
int32_t *skinJoints; // Nodes whose world matrices drive a skinned mesh
|
int32_t *skinJoints; // Nodes whose world matrices drive a skinned mesh
|
||||||
Mat4T *skinInverseBind;
|
Mat4T *skinInverseBind;
|
||||||
int32_t skinCount;
|
int32_t skinCount;
|
||||||
|
|
@ -163,8 +192,18 @@ typedef struct SceneS {
|
||||||
SDL_GPUShader *vertexStatic;
|
SDL_GPUShader *vertexStatic;
|
||||||
SDL_GPUShader *vertexSkinned;
|
SDL_GPUShader *vertexSkinned;
|
||||||
SDL_GPUShader *fragment;
|
SDL_GPUShader *fragment;
|
||||||
|
SDL_GPUShader *depthFragment; // Empty; the shadow pass writes depth only
|
||||||
SDL_GPUGraphicsPipeline *pipelines[PIPELINE_COUNT];
|
SDL_GPUGraphicsPipeline *pipelines[PIPELINE_COUNT];
|
||||||
|
SDL_GPUGraphicsPipeline *shadowPipelines[SHADOW_PIPELINES];
|
||||||
SDL_GPUSampler *sampler;
|
SDL_GPUSampler *sampler;
|
||||||
|
SDL_GPUSampler *shadowSampler;
|
||||||
|
SDL_GPUTexture *shadowMaps; // 2D array: a layer per directional or spot shadow, six per point light
|
||||||
|
SDL_GPUTexture *shadowMapsNone; // 1x1 stand-in bound when nothing casts, so the shader's slot is filled
|
||||||
|
int32_t shadowMapLayers;
|
||||||
|
SDL_GPUTextureFormat shadowFormat;
|
||||||
|
int32_t shadowSize;
|
||||||
|
ShadowT shadows[MAX_SHADOWS];
|
||||||
|
int32_t shadowCount;
|
||||||
SDL_GPUTexture *white; // 1x1 stand-in for untextured materials
|
SDL_GPUTexture *white; // 1x1 stand-in for untextured materials
|
||||||
SDL_FColor background;
|
SDL_FColor background;
|
||||||
Vec3T ambient;
|
Vec3T ambient;
|
||||||
|
|
@ -198,13 +237,20 @@ static int32_t _allocNode(void);
|
||||||
static void _attach(int32_t node, int32_t parent);
|
static void _attach(int32_t node, int32_t parent);
|
||||||
static int32_t _compareDraws(const void *a, const void *b);
|
static int32_t _compareDraws(const void *a, const void *b);
|
||||||
static bool _createPipeline(int32_t variant);
|
static bool _createPipeline(int32_t variant);
|
||||||
|
static SDL_GPUTexture *_createShadowArray(SDL_GPUTextureType type, int32_t layers, int32_t size);
|
||||||
|
static bool _createShadowMaps(int32_t layers);
|
||||||
|
static bool _createShadowPipeline(int32_t variant);
|
||||||
static SDL_GPUShader *_createShader(const SceneShaderT *shader, SDL_GPUShaderStage stage, uint32_t samplers, uint32_t uniforms);
|
static SDL_GPUShader *_createShader(const SceneShaderT *shader, SDL_GPUShaderStage stage, uint32_t samplers, uint32_t uniforms);
|
||||||
static bool _createShaders(void);
|
static bool _createShaders(void);
|
||||||
static SDL_GPUTextureFormat _depthFormat(void);
|
static SDL_GPUTextureFormat _depthFormat(void);
|
||||||
|
static SDL_GPUTextureFormat _shadowFormat(void);
|
||||||
static void _destroyTargets(void);
|
static void _destroyTargets(void);
|
||||||
static void _detach(int32_t node);
|
static void _detach(int32_t node);
|
||||||
static void _destroyPipelines(void);
|
static void _destroyPipelines(void);
|
||||||
|
static void _destroyShadowMaps(void);
|
||||||
|
static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, int32_t drawCount, const Mat4T *viewProjection, bool shadowPass, FragmentUniformsT *fragmentUniforms);
|
||||||
static void _fillLights(FragmentUniformsT *uniforms);
|
static void _fillLights(FragmentUniformsT *uniforms);
|
||||||
|
static void _fitShadows(int32_t drawCount);
|
||||||
static void _fillSkin(const NodeT *node, SkinUniformsT *uniforms);
|
static void _fillSkin(const NodeT *node, SkinUniformsT *uniforms);
|
||||||
static void _freeFeed(FeedT *feed);
|
static void _freeFeed(FeedT *feed);
|
||||||
static void _freeSkin(NodeT *node);
|
static void _freeSkin(NodeT *node);
|
||||||
|
|
@ -257,7 +303,24 @@ static int32_t _addMesh(const SceneVertexT *vertices, int32_t vertexCount, const
|
||||||
mesh->indexCount = (uint32_t)indexCount;
|
mesh->indexCount = (uint32_t)indexCount;
|
||||||
mesh->skinned = skinned;
|
mesh->skinned = skinned;
|
||||||
mesh->used = true;
|
mesh->used = true;
|
||||||
return x;
|
mesh->boundsMin = vec3(vertices[0].position[0], vertices[0].position[1], vertices[0].position[2]);
|
||||||
|
mesh->boundsMax = mesh->boundsMin;
|
||||||
|
for (x = 1; x < vertexCount; x++) {
|
||||||
|
mesh->boundsMin.x = SDL_min(mesh->boundsMin.x, vertices[x].position[0]);
|
||||||
|
mesh->boundsMin.y = SDL_min(mesh->boundsMin.y, vertices[x].position[1]);
|
||||||
|
mesh->boundsMin.z = SDL_min(mesh->boundsMin.z, vertices[x].position[2]);
|
||||||
|
mesh->boundsMax.x = SDL_max(mesh->boundsMax.x, vertices[x].position[0]);
|
||||||
|
mesh->boundsMax.y = SDL_max(mesh->boundsMax.y, vertices[x].position[1]);
|
||||||
|
mesh->boundsMax.z = SDL_max(mesh->boundsMax.z, vertices[x].position[2]);
|
||||||
|
}
|
||||||
|
if (skinned) {
|
||||||
|
Vec3T centre = vec3Scale(vec3Add(mesh->boundsMin, mesh->boundsMax), 0.5f);
|
||||||
|
Vec3T half = vec3Scale(vec3Subtract(mesh->boundsMax, mesh->boundsMin), 0.5f * SKIN_BOUNDS_GROW);
|
||||||
|
|
||||||
|
mesh->boundsMin = vec3Subtract(centre, half);
|
||||||
|
mesh->boundsMax = vec3Add(centre, half);
|
||||||
|
}
|
||||||
|
return (int32_t)(mesh - _scene.meshes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -490,8 +553,101 @@ static SDL_GPUShader *_createShader(const SceneShaderT *shader, SDL_GPUShaderSta
|
||||||
static bool _createShaders(void) {
|
static bool _createShaders(void) {
|
||||||
_scene.vertexStatic = _createShader(&sceneShaderVertexStatic, SDL_GPU_SHADERSTAGE_VERTEX, 0, 1);
|
_scene.vertexStatic = _createShader(&sceneShaderVertexStatic, SDL_GPU_SHADERSTAGE_VERTEX, 0, 1);
|
||||||
_scene.vertexSkinned = _createShader(&sceneShaderVertexSkinned, SDL_GPU_SHADERSTAGE_VERTEX, 0, 2);
|
_scene.vertexSkinned = _createShader(&sceneShaderVertexSkinned, SDL_GPU_SHADERSTAGE_VERTEX, 0, 2);
|
||||||
_scene.fragment = _createShader(&sceneShaderFragmentMain, SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
_scene.fragment = _createShader(&sceneShaderFragmentMain, SDL_GPU_SHADERSTAGE_FRAGMENT, 2, 1);
|
||||||
return (_scene.vertexStatic != NULL) && (_scene.vertexSkinned != NULL) && (_scene.fragment != NULL);
|
_scene.depthFragment = _createShader(&sceneShaderDepthMain, SDL_GPU_SHADERSTAGE_FRAGMENT, 0, 0);
|
||||||
|
return (_scene.vertexStatic != NULL) && (_scene.vertexSkinned != NULL) && (_scene.fragment != NULL) && (_scene.depthFragment != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A depth texture array the shadow passes render into and the main pass samples.
|
||||||
|
static SDL_GPUTexture *_createShadowArray(SDL_GPUTextureType type, int32_t layers, int32_t size) {
|
||||||
|
SDL_GPUTextureCreateInfo info;
|
||||||
|
SDL_GPUTexture *texture;
|
||||||
|
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
info.type = type;
|
||||||
|
info.format = _scene.shadowFormat;
|
||||||
|
info.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||||
|
info.width = (Uint32)size;
|
||||||
|
info.height = (Uint32)size;
|
||||||
|
info.layer_count_or_depth = (Uint32)layers;
|
||||||
|
info.num_levels = 1;
|
||||||
|
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
texture = SDL_CreateGPUTexture(_scene.device, &info);
|
||||||
|
if (texture == NULL) {
|
||||||
|
utilTrace("Scene: shadow map: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Makes sure the array has room for this frame's shadows; it grows to the most used so far.
|
||||||
|
static bool _createShadowMaps(int32_t layers) {
|
||||||
|
if (layers > _scene.shadowMapLayers) {
|
||||||
|
if (_scene.shadowMaps != NULL) {
|
||||||
|
SDL_ReleaseGPUTexture(_scene.device, _scene.shadowMaps);
|
||||||
|
}
|
||||||
|
_scene.shadowMaps = _createShadowArray(SDL_GPU_TEXTURETYPE_2D_ARRAY, layers, _scene.shadowSize);
|
||||||
|
_scene.shadowMapLayers = (_scene.shadowMaps != NULL) ? layers : 0;
|
||||||
|
}
|
||||||
|
return _scene.shadowMaps != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Depth-only pipelines for the shadow pass: the scene's vertex shaders with an empty fragment
|
||||||
|
// shader, no colour target, and a depth bias against self-shadowing.
|
||||||
|
static bool _createShadowPipeline(int32_t variant) {
|
||||||
|
SDL_GPUGraphicsPipelineCreateInfo info;
|
||||||
|
SDL_GPUVertexBufferDescription buffers[1];
|
||||||
|
SDL_GPUVertexAttribute attributes[5];
|
||||||
|
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
memset(buffers, 0, sizeof(buffers));
|
||||||
|
memset(attributes, 0, sizeof(attributes));
|
||||||
|
buffers[0].slot = 0;
|
||||||
|
buffers[0].pitch = sizeof(SceneVertexT);
|
||||||
|
buffers[0].input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||||
|
attributes[0].location = 0;
|
||||||
|
attributes[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
|
||||||
|
attributes[0].offset = offsetof(SceneVertexT, position);
|
||||||
|
attributes[1].location = 1;
|
||||||
|
attributes[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
|
||||||
|
attributes[1].offset = offsetof(SceneVertexT, normal);
|
||||||
|
attributes[2].location = 2;
|
||||||
|
attributes[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||||
|
attributes[2].offset = offsetof(SceneVertexT, uv);
|
||||||
|
attributes[3].location = 3;
|
||||||
|
attributes[3].format = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4;
|
||||||
|
attributes[3].offset = offsetof(SceneVertexT, joints);
|
||||||
|
attributes[4].location = 4;
|
||||||
|
attributes[4].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||||
|
attributes[4].offset = offsetof(SceneVertexT, weights);
|
||||||
|
info.vertex_shader = (variant & PIPELINE_SKINNED) ? _scene.vertexSkinned : _scene.vertexStatic;
|
||||||
|
info.fragment_shader = _scene.depthFragment;
|
||||||
|
info.vertex_input_state.vertex_buffer_descriptions = buffers;
|
||||||
|
info.vertex_input_state.num_vertex_buffers = 1;
|
||||||
|
info.vertex_input_state.vertex_attributes = attributes;
|
||||||
|
info.vertex_input_state.num_vertex_attributes = 5;
|
||||||
|
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||||
|
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
||||||
|
info.rasterizer_state.cull_mode = (variant & PIPELINE_TWO_SIDED) ? SDL_GPU_CULLMODE_NONE : SDL_GPU_CULLMODE_BACK;
|
||||||
|
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||||
|
info.rasterizer_state.enable_depth_bias = true;
|
||||||
|
info.rasterizer_state.depth_bias_constant_factor = 2.0f;
|
||||||
|
info.rasterizer_state.depth_bias_slope_factor = 2.0f;
|
||||||
|
info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
info.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
|
||||||
|
info.depth_stencil_state.enable_depth_test = true;
|
||||||
|
info.depth_stencil_state.enable_depth_write = true;
|
||||||
|
info.target_info.num_color_targets = 0;
|
||||||
|
info.target_info.depth_stencil_format = _scene.shadowFormat;
|
||||||
|
info.target_info.has_depth_stencil_target = true;
|
||||||
|
_scene.shadowPipelines[variant] = SDL_CreateGPUGraphicsPipeline(_scene.device, &info);
|
||||||
|
if (_scene.shadowPipelines[variant] == NULL) {
|
||||||
|
utilTrace("Scene: shadow pipeline %d: %s", variant, SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -520,6 +676,21 @@ static void _destroyPipelines(void) {
|
||||||
_scene.pipelines[x] = NULL;
|
_scene.pipelines[x] = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (x = 0; x < SHADOW_PIPELINES; x++) {
|
||||||
|
if (_scene.shadowPipelines[x] != NULL) {
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(_scene.device, _scene.shadowPipelines[x]);
|
||||||
|
_scene.shadowPipelines[x] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void _destroyShadowMaps(void) {
|
||||||
|
if (_scene.shadowMaps != NULL) {
|
||||||
|
SDL_ReleaseGPUTexture(_scene.device, _scene.shadowMaps);
|
||||||
|
_scene.shadowMaps = NULL;
|
||||||
|
}
|
||||||
|
_scene.shadowMapLayers = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -546,6 +717,108 @@ static void _destroyTargets(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Issues the collected draws into a pass: the shadow pass with the light's view-projection and
|
||||||
|
// depth-only pipelines (blended meshes cast nothing), or the main pass with the camera's and the
|
||||||
|
// full material.
|
||||||
|
static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, int32_t drawCount, const Mat4T *viewProjection, bool shadowPass, FragmentUniformsT *fragmentUniforms) {
|
||||||
|
SDL_GPUBufferBinding binding;
|
||||||
|
SDL_GPUTextureSamplerBinding samplerBindings[2];
|
||||||
|
DrawUniformsT drawUniforms;
|
||||||
|
SkinUniformsT *skinUniforms = NULL;
|
||||||
|
Mat4T identity = mat4Identity();
|
||||||
|
Mat4T inverse;
|
||||||
|
int32_t x;
|
||||||
|
int32_t lastPipeline = NO_HANDLE;
|
||||||
|
int32_t variant;
|
||||||
|
NodeT *node;
|
||||||
|
MeshT *mesh;
|
||||||
|
MaterialT *material;
|
||||||
|
MaterialT defaultMaterial;
|
||||||
|
SDL_GPUGraphicsPipeline *pipeline;
|
||||||
|
|
||||||
|
memset(&defaultMaterial, 0, sizeof(defaultMaterial));
|
||||||
|
defaultMaterial.baseColor.x = 1.0f;
|
||||||
|
defaultMaterial.baseColor.y = 1.0f;
|
||||||
|
defaultMaterial.baseColor.z = 1.0f;
|
||||||
|
defaultMaterial.baseColor.w = 1.0f;
|
||||||
|
defaultMaterial.roughness = 0.5f;
|
||||||
|
defaultMaterial.feed = NO_HANDLE;
|
||||||
|
for (x = 0; x < drawCount; x++) {
|
||||||
|
node = &_scene.nodes[_scene.draws[x].node];
|
||||||
|
mesh = &_scene.meshes[node->mesh];
|
||||||
|
material = (node->material != NO_HANDLE) ? &_scene.materials[node->material] : &defaultMaterial;
|
||||||
|
variant = _lookupPipeline(_scene.draws[x].node);
|
||||||
|
if (variant == NO_HANDLE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (shadowPass) {
|
||||||
|
if (material->blend) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
variant &= PIPELINE_SKINNED | PIPELINE_TWO_SIDED;
|
||||||
|
variant = (variant & PIPELINE_SKINNED ? 1 : 0) | (variant & PIPELINE_TWO_SIDED ? 2 : 0);
|
||||||
|
if ((_scene.shadowPipelines[variant] == NULL) && !_createShadowPipeline(variant)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pipeline = _scene.shadowPipelines[variant];
|
||||||
|
} else {
|
||||||
|
pipeline = _scene.pipelines[variant];
|
||||||
|
}
|
||||||
|
if (variant != lastPipeline) {
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, pipeline);
|
||||||
|
lastPipeline = variant;
|
||||||
|
}
|
||||||
|
drawUniforms.modelViewProjection = mat4Multiply(*viewProjection, node->world);
|
||||||
|
drawUniforms.model = node->world;
|
||||||
|
if (mat4Invert(node->world, &inverse)) {
|
||||||
|
drawUniforms.normalMatrix = mat4Transpose(inverse);
|
||||||
|
} else {
|
||||||
|
drawUniforms.normalMatrix = identity;
|
||||||
|
}
|
||||||
|
SDL_PushGPUVertexUniformData(commands, 0, &drawUniforms, sizeof(drawUniforms));
|
||||||
|
if (mesh->skinned && (node->skinCount > 0)) {
|
||||||
|
// 8 KB per skinned draw; allocated once per pass that needs it.
|
||||||
|
if (skinUniforms == NULL) {
|
||||||
|
skinUniforms = SDL_malloc(sizeof(SkinUniformsT));
|
||||||
|
if (skinUniforms == NULL) {
|
||||||
|
utilDie("Out of memory posing a skin.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_fillSkin(node, skinUniforms);
|
||||||
|
SDL_PushGPUVertexUniformData(commands, 1, skinUniforms, sizeof(SkinUniformsT));
|
||||||
|
}
|
||||||
|
if (!shadowPass) {
|
||||||
|
fragmentUniforms->baseColor[0] = material->baseColor.x;
|
||||||
|
fragmentUniforms->baseColor[1] = material->baseColor.y;
|
||||||
|
fragmentUniforms->baseColor[2] = material->baseColor.z;
|
||||||
|
fragmentUniforms->baseColor[3] = material->baseColor.w;
|
||||||
|
fragmentUniforms->emissive[0] = material->emissive.x;
|
||||||
|
fragmentUniforms->emissive[1] = material->emissive.y;
|
||||||
|
fragmentUniforms->emissive[2] = material->emissive.z;
|
||||||
|
fragmentUniforms->emissive[3] = 1.0f;
|
||||||
|
fragmentUniforms->material[0] = material->metallic;
|
||||||
|
fragmentUniforms->material[1] = material->roughness;
|
||||||
|
fragmentUniforms->material[2] = material->unlit ? 1.0f : 0.0f;
|
||||||
|
fragmentUniforms->material[3] = (_materialTexture(material) != NULL) ? 1.0f : 0.0f;
|
||||||
|
SDL_PushGPUFragmentUniformData(commands, 0, fragmentUniforms, sizeof(FragmentUniformsT));
|
||||||
|
memset(samplerBindings, 0, sizeof(samplerBindings));
|
||||||
|
samplerBindings[0].texture = (_materialTexture(material) != NULL) ? _materialTexture(material) : _scene.white;
|
||||||
|
samplerBindings[0].sampler = _scene.sampler;
|
||||||
|
samplerBindings[1].texture = (_scene.shadowMaps != NULL) ? _scene.shadowMaps : _scene.shadowMapsNone;
|
||||||
|
samplerBindings[1].sampler = _scene.shadowSampler;
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, samplerBindings, 2);
|
||||||
|
}
|
||||||
|
memset(&binding, 0, sizeof(binding));
|
||||||
|
binding.buffer = mesh->vertexBuffer;
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
||||||
|
binding.buffer = mesh->indexBuffer;
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &binding, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, mesh->indexCount, 1, 0, 0, 0);
|
||||||
|
}
|
||||||
|
SDL_free(skinUniforms);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Unlinks node from its parent's child list.
|
// Unlinks node from its parent's child list.
|
||||||
static void _detach(int32_t node) {
|
static void _detach(int32_t node) {
|
||||||
int32_t parent = _scene.nodes[node].parent;
|
int32_t parent = _scene.nodes[node].parent;
|
||||||
|
|
@ -570,10 +843,12 @@ static void _detach(int32_t node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The first MAX_LIGHTS visible lights, in world space.
|
// The first MAX_LIGHTS visible lights, in world space, and the shadow slots: every one of them
|
||||||
|
// flagged to cast gets a slot (a map for directional and spot lights, a cube for point lights).
|
||||||
static void _fillLights(FragmentUniformsT *uniforms) {
|
static void _fillLights(FragmentUniformsT *uniforms) {
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t count = 0;
|
int32_t count = 0;
|
||||||
|
int32_t layers = 0;
|
||||||
NodeT *node;
|
NodeT *node;
|
||||||
LightUniformT *light;
|
LightUniformT *light;
|
||||||
Vec3T position;
|
Vec3T position;
|
||||||
|
|
@ -603,6 +878,22 @@ static void _fillLights(FragmentUniformsT *uniforms) {
|
||||||
light->cone[1] = cosf(node->light.outerDegrees * PI / 180.0f);
|
light->cone[1] = cosf(node->light.outerDegrees * PI / 180.0f);
|
||||||
light->cone[2] = 0.0f;
|
light->cone[2] = 0.0f;
|
||||||
light->cone[3] = 0.0f;
|
light->cone[3] = 0.0f;
|
||||||
|
if (node->castsShadow && (_scene.shadowCount < MAX_SHADOWS)) {
|
||||||
|
ShadowT *shadow = &_scene.shadows[_scene.shadowCount];
|
||||||
|
|
||||||
|
memset(shadow, 0, sizeof(*shadow));
|
||||||
|
shadow->node = x;
|
||||||
|
shadow->layer = layers;
|
||||||
|
if (node->light.type == LIGHT_POINT) {
|
||||||
|
shadow->type = SHADOW_CUBE;
|
||||||
|
layers += CUBE_FACES;
|
||||||
|
} else {
|
||||||
|
shadow->type = SHADOW_MAP;
|
||||||
|
layers++;
|
||||||
|
}
|
||||||
|
light->cone[2] = (float)(_scene.shadowCount + 1);
|
||||||
|
_scene.shadowCount++;
|
||||||
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
uniforms->counts[0] = (float)count;
|
uniforms->counts[0] = (float)count;
|
||||||
|
|
@ -633,6 +924,102 @@ static void _fillSkin(const NodeT *node, SkinUniformsT *uniforms) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Every shadow's projection, fitted to what is drawn: a directional light gets a parallel box
|
||||||
|
// round the scene's bounds, a spot light its own cone, a point light six 90 degree faces out to
|
||||||
|
// its range or the far edge of the scene.
|
||||||
|
static void _fitShadows(int32_t drawCount) {
|
||||||
|
Vec3T corners[8];
|
||||||
|
Vec3T boundsMin = vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
Vec3T boundsMax = vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
Vec3T centre;
|
||||||
|
float radius;
|
||||||
|
int32_t x;
|
||||||
|
int32_t c;
|
||||||
|
int32_t slot;
|
||||||
|
bool any = false;
|
||||||
|
// The six faces of a point light's shadow: the direction each looks and its up. The fragment
|
||||||
|
// shader rebuilds the same frames to look them up, so these need only match it.
|
||||||
|
Vec3T faceForward[CUBE_FACES] = { { 1.0f, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, -1.0f } };
|
||||||
|
Vec3T faceUp[CUBE_FACES] = { { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } };
|
||||||
|
|
||||||
|
// World bounds of everything drawn.
|
||||||
|
for (x = 0; x < drawCount; x++) {
|
||||||
|
NodeT *node = &_scene.nodes[_scene.draws[x].node];
|
||||||
|
MeshT *mesh = &_scene.meshes[node->mesh];
|
||||||
|
|
||||||
|
for (c = 0; c < 8; c++) {
|
||||||
|
Vec3T corner = vec3((c & 1) ? mesh->boundsMax.x : mesh->boundsMin.x, (c & 2) ? mesh->boundsMax.y : mesh->boundsMin.y, (c & 4) ? mesh->boundsMax.z : mesh->boundsMin.z);
|
||||||
|
|
||||||
|
corner = mat4TransformPoint(node->world, corner);
|
||||||
|
if (!any) {
|
||||||
|
boundsMin = corner;
|
||||||
|
boundsMax = corner;
|
||||||
|
any = true;
|
||||||
|
} else {
|
||||||
|
boundsMin = vec3(SDL_min(boundsMin.x, corner.x), SDL_min(boundsMin.y, corner.y), SDL_min(boundsMin.z, corner.z));
|
||||||
|
boundsMax = vec3(SDL_max(boundsMax.x, corner.x), SDL_max(boundsMax.y, corner.y), SDL_max(boundsMax.z, corner.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
centre = vec3Scale(vec3Add(boundsMin, boundsMax), 0.5f);
|
||||||
|
radius = vec3Length(vec3Subtract(boundsMax, centre)) * SHADOW_MARGIN + 0.001f;
|
||||||
|
for (c = 0; c < 8; c++) {
|
||||||
|
corners[c] = vec3((c & 1) ? boundsMax.x : boundsMin.x, (c & 2) ? boundsMax.y : boundsMin.y, (c & 4) ? boundsMax.z : boundsMin.z);
|
||||||
|
}
|
||||||
|
for (slot = 0; slot < _scene.shadowCount; slot++) {
|
||||||
|
ShadowT *shadow = &_scene.shadows[slot];
|
||||||
|
NodeT *light = &_scene.nodes[shadow->node];
|
||||||
|
Vec3T direction = vec3Normalize(mat4TransformVector(light->world, vec3(0.0f, 0.0f, -1.0f)));
|
||||||
|
Vec3T position = mat4TransformPoint(light->world, vec3(0.0f, 0.0f, 0.0f));
|
||||||
|
Vec3T up = (fabsf(direction.y) < 0.99f) ? vec3(0.0f, 1.0f, 0.0f) : vec3(0.0f, 0.0f, 1.0f);
|
||||||
|
Mat4T view;
|
||||||
|
float far;
|
||||||
|
float minX = 0.0f;
|
||||||
|
float maxX = 0.0f;
|
||||||
|
float minY = 0.0f;
|
||||||
|
float maxY = 0.0f;
|
||||||
|
float minZ = 0.0f;
|
||||||
|
float maxZ = 0.0f;
|
||||||
|
|
||||||
|
if (light->light.type == LIGHT_DIRECTIONAL) {
|
||||||
|
view = mat4LookAt(vec3Subtract(centre, vec3Scale(direction, radius * 2.0f)), centre, up);
|
||||||
|
for (c = 0; c < 8; c++) {
|
||||||
|
Vec3T v = mat4TransformPoint(view, corners[c]);
|
||||||
|
|
||||||
|
if (c == 0) {
|
||||||
|
minX = v.x;
|
||||||
|
maxX = v.x;
|
||||||
|
minY = v.y;
|
||||||
|
maxY = v.y;
|
||||||
|
minZ = v.z;
|
||||||
|
maxZ = v.z;
|
||||||
|
} else {
|
||||||
|
minX = SDL_min(minX, v.x);
|
||||||
|
maxX = SDL_max(maxX, v.x);
|
||||||
|
minY = SDL_min(minY, v.y);
|
||||||
|
maxY = SDL_max(maxY, v.y);
|
||||||
|
minZ = SDL_min(minZ, v.z);
|
||||||
|
maxZ = SDL_max(maxZ, v.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// View space looks down -Z: the nearest point has the largest z.
|
||||||
|
shadow->matrix = mat4Multiply(mat4OrthographicBounds(minX * SHADOW_MARGIN, maxX * SHADOW_MARGIN, minY * SHADOW_MARGIN, maxY * SHADOW_MARGIN, SDL_max(-maxZ / SHADOW_MARGIN, 0.01f), -minZ * SHADOW_MARGIN), view);
|
||||||
|
} else if (light->light.type == LIGHT_SPOT) {
|
||||||
|
view = mat4LookAt(position, vec3Add(position, direction), up);
|
||||||
|
far = (light->light.range > 0.0f) ? light->light.range : vec3Length(vec3Subtract(centre, position)) + radius;
|
||||||
|
shadow->matrix = mat4Multiply(mat4Perspective(SDL_min(light->light.outerDegrees * 2.0f * SHADOW_MARGIN, 170.0f), 1.0f, SDL_max(far * 0.005f, 0.01f), far), view);
|
||||||
|
} else {
|
||||||
|
far = (light->light.range > 0.0f) ? light->light.range : vec3Length(vec3Subtract(centre, position)) + radius;
|
||||||
|
shadow->near = SDL_max(far * 0.005f, 0.01f);
|
||||||
|
shadow->far = far;
|
||||||
|
for (c = 0; c < CUBE_FACES; c++) {
|
||||||
|
shadow->faces[c] = mat4Multiply(mat4Perspective(90.0f, 1.0f, shadow->near, shadow->far), mat4LookAt(position, vec3Add(position, faceForward[c]), faceUp[c]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _freeFeed(FeedT *feed) {
|
static void _freeFeed(FeedT *feed) {
|
||||||
if (feed->target != NULL) {
|
if (feed->target != NULL) {
|
||||||
SDL_DestroyTexture(feed->target);
|
SDL_DestroyTexture(feed->target);
|
||||||
|
|
@ -772,6 +1159,20 @@ static Mat4T _projection(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A depth format the shadow map can be both rendered into and sampled from.
|
||||||
|
static SDL_GPUTextureFormat _shadowFormat(void) {
|
||||||
|
SDL_GPUTextureFormat wanted[] = { SDL_GPU_TEXTUREFORMAT_D32_FLOAT, SDL_GPU_TEXTUREFORMAT_D16_UNORM, SDL_GPU_TEXTUREFORMAT_D24_UNORM };
|
||||||
|
int32_t x;
|
||||||
|
|
||||||
|
for (x = 0; x < (int32_t)SDL_arraysize(wanted); x++) {
|
||||||
|
if (SDL_GPUTextureSupportsFormat(_scene.device, wanted[x], SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER)) {
|
||||||
|
return wanted[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SDL_GPU_TEXTUREFORMAT_D16_UNORM;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// World matrices and inherited visibility, depth first.
|
// World matrices and inherited visibility, depth first.
|
||||||
static void _updateWorld(int32_t node, const Mat4T *parentWorld, bool parentVisible) {
|
static void _updateWorld(int32_t node, const Mat4T *parentWorld, bool parentVisible) {
|
||||||
NodeT *n = &_scene.nodes[node];
|
NodeT *n = &_scene.nodes[node];
|
||||||
|
|
@ -1040,6 +1441,17 @@ bool lightSetRange(int32_t node, float range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Whether this light casts shadows. Every light may; a point light's cost six passes to a
|
||||||
|
// directional or spot light's one.
|
||||||
|
bool lightSetShadow(int32_t node, bool shadow) {
|
||||||
|
if (!nodeValid(node) || !_scene.nodes[node].hasLight) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_scene.nodes[node].castsShadow = shadow;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===== Materials =====
|
// ===== Materials =====
|
||||||
|
|
||||||
bool materialDelete(int32_t material) {
|
bool materialDelete(int32_t material) {
|
||||||
|
|
@ -1801,10 +2213,12 @@ bool sceneInit(SDL_GPUDevice *device, SDL_Renderer *renderer) {
|
||||||
_scene.ambient = vec3(0.1f, 0.1f, 0.1f);
|
_scene.ambient = vec3(0.1f, 0.1f, 0.1f);
|
||||||
_scene.antialias = true;
|
_scene.antialias = true;
|
||||||
_scene.sampleCount = SDL_GPU_SAMPLECOUNT_1;
|
_scene.sampleCount = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
_scene.shadowSize = SHADOW_SIZE;
|
||||||
if (device == NULL) {
|
if (device == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_scene.depthFormat = _depthFormat();
|
_scene.depthFormat = _depthFormat();
|
||||||
|
_scene.shadowFormat = _shadowFormat();
|
||||||
_allocNode();
|
_allocNode();
|
||||||
nodeSetName(SCENE_ROOT_NODE, "root");
|
nodeSetName(SCENE_ROOT_NODE, "root");
|
||||||
if (!_createShaders()) {
|
if (!_createShaders()) {
|
||||||
|
|
@ -1819,13 +2233,22 @@ bool sceneInit(SDL_GPUDevice *device, SDL_Renderer *renderer) {
|
||||||
samplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
|
samplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
|
||||||
samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
|
samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
|
||||||
_scene.sampler = SDL_CreateGPUSampler(device, &samplerInfo);
|
_scene.sampler = SDL_CreateGPUSampler(device, &samplerInfo);
|
||||||
|
// The shadow map is compared texel by texel, so no filtering, and clamped so its edge holds.
|
||||||
|
samplerInfo.min_filter = SDL_GPU_FILTER_NEAREST;
|
||||||
|
samplerInfo.mag_filter = SDL_GPU_FILTER_NEAREST;
|
||||||
|
samplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||||
|
samplerInfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||||
|
samplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||||
|
samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||||
|
_scene.shadowSampler = SDL_CreateGPUSampler(device, &samplerInfo);
|
||||||
pixel = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
|
pixel = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
|
||||||
if (pixel != NULL) {
|
if (pixel != NULL) {
|
||||||
SDL_FillSurfaceRect(pixel, NULL, 0xFFFFFFFFu);
|
SDL_FillSurfaceRect(pixel, NULL, 0xFFFFFFFFu);
|
||||||
_scene.white = _uploadTexture(pixel);
|
_scene.white = _uploadTexture(pixel);
|
||||||
SDL_DestroySurface(pixel);
|
SDL_DestroySurface(pixel);
|
||||||
}
|
}
|
||||||
if ((_scene.sampler == NULL) || (_scene.white == NULL)) {
|
_scene.shadowMapsNone = _createShadowArray(SDL_GPU_TEXTURETYPE_2D_ARRAY, 1, 1);
|
||||||
|
if ((_scene.sampler == NULL) || (_scene.shadowSampler == NULL) || (_scene.white == NULL) || (_scene.shadowMapsNone == NULL)) {
|
||||||
utilTrace("Scene: %s", SDL_GetError());
|
utilTrace("Scene: %s", SDL_GetError());
|
||||||
sceneQuit();
|
sceneQuit();
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1880,9 +2303,19 @@ void sceneQuit(void) {
|
||||||
if (_scene.fragment != NULL) {
|
if (_scene.fragment != NULL) {
|
||||||
SDL_ReleaseGPUShader(_scene.device, _scene.fragment);
|
SDL_ReleaseGPUShader(_scene.device, _scene.fragment);
|
||||||
}
|
}
|
||||||
|
if (_scene.depthFragment != NULL) {
|
||||||
|
SDL_ReleaseGPUShader(_scene.device, _scene.depthFragment);
|
||||||
|
}
|
||||||
if (_scene.sampler != NULL) {
|
if (_scene.sampler != NULL) {
|
||||||
SDL_ReleaseGPUSampler(_scene.device, _scene.sampler);
|
SDL_ReleaseGPUSampler(_scene.device, _scene.sampler);
|
||||||
}
|
}
|
||||||
|
if (_scene.shadowSampler != NULL) {
|
||||||
|
SDL_ReleaseGPUSampler(_scene.device, _scene.shadowSampler);
|
||||||
|
}
|
||||||
|
_destroyShadowMaps();
|
||||||
|
if (_scene.shadowMapsNone != NULL) {
|
||||||
|
SDL_ReleaseGPUTexture(_scene.device, _scene.shadowMapsNone);
|
||||||
|
}
|
||||||
if (_scene.white != NULL) {
|
if (_scene.white != NULL) {
|
||||||
SDL_ReleaseGPUTexture(_scene.device, _scene.white);
|
SDL_ReleaseGPUTexture(_scene.device, _scene.white);
|
||||||
}
|
}
|
||||||
|
|
@ -1909,24 +2342,17 @@ SDL_Texture *sceneRender(void) {
|
||||||
SDL_GPUDepthStencilTargetInfo depth;
|
SDL_GPUDepthStencilTargetInfo depth;
|
||||||
SDL_GPUCommandBuffer *commands;
|
SDL_GPUCommandBuffer *commands;
|
||||||
SDL_GPURenderPass *pass;
|
SDL_GPURenderPass *pass;
|
||||||
SDL_GPUBufferBinding binding;
|
|
||||||
SDL_GPUTextureSamplerBinding samplerBinding;
|
|
||||||
DrawUniformsT drawUniforms;
|
|
||||||
SkinUniformsT *skinUniforms = NULL;
|
|
||||||
FragmentUniformsT fragmentUniforms;
|
FragmentUniformsT fragmentUniforms;
|
||||||
Mat4T identity = mat4Identity();
|
Mat4T identity = mat4Identity();
|
||||||
Mat4T view;
|
Mat4T view;
|
||||||
Mat4T inverse;
|
|
||||||
Vec3T eye;
|
Vec3T eye;
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t drawCount = 0;
|
int32_t drawCount = 0;
|
||||||
int32_t opaqueCount = 0;
|
int32_t opaqueCount = 0;
|
||||||
int32_t lastPipeline = NO_HANDLE;
|
int32_t slot;
|
||||||
int32_t variant;
|
int32_t face;
|
||||||
|
int32_t layers = 0;
|
||||||
NodeT *node;
|
NodeT *node;
|
||||||
MeshT *mesh;
|
|
||||||
MaterialT *material;
|
|
||||||
MaterialT defaultMaterial;
|
|
||||||
|
|
||||||
if (!_scene.enabled || (_scene.colour == NULL)) {
|
if (!_scene.enabled || (_scene.colour == NULL)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -1945,6 +2371,7 @@ SDL_Texture *sceneRender(void) {
|
||||||
fragmentUniforms.ambient[1] = _scene.ambient.y;
|
fragmentUniforms.ambient[1] = _scene.ambient.y;
|
||||||
fragmentUniforms.ambient[2] = _scene.ambient.z;
|
fragmentUniforms.ambient[2] = _scene.ambient.z;
|
||||||
fragmentUniforms.ambient[3] = 1.0f;
|
fragmentUniforms.ambient[3] = 1.0f;
|
||||||
|
_scene.shadowCount = 0;
|
||||||
_fillLights(&fragmentUniforms);
|
_fillLights(&fragmentUniforms);
|
||||||
// Collect what to draw: opaque first in node order, then blended back to front.
|
// Collect what to draw: opaque first in node order, then blended back to front.
|
||||||
if (_scene.drawCapacity < _scene.nodeCount) {
|
if (_scene.drawCapacity < _scene.nodeCount) {
|
||||||
|
|
@ -1980,12 +2407,57 @@ SDL_Texture *sceneRender(void) {
|
||||||
if (drawCount > opaqueCount) {
|
if (drawCount > opaqueCount) {
|
||||||
qsort(&_scene.draws[opaqueCount], (size_t)(drawCount - opaqueCount), sizeof(DrawT), _compareDraws);
|
qsort(&_scene.draws[opaqueCount], (size_t)(drawCount - opaqueCount), sizeof(DrawT), _compareDraws);
|
||||||
}
|
}
|
||||||
// Record the pass.
|
|
||||||
commands = SDL_AcquireGPUCommandBuffer(_scene.device);
|
commands = SDL_AcquireGPUCommandBuffer(_scene.device);
|
||||||
if (commands == NULL) {
|
if (commands == NULL) {
|
||||||
utilTrace("Scene: %s", SDL_GetError());
|
utilTrace("Scene: %s", SDL_GetError());
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
// The shadow passes: depth from every shadow light into its map layer, or its six cube faces.
|
||||||
|
for (slot = 0; slot < _scene.shadowCount; slot++) {
|
||||||
|
layers += (_scene.shadows[slot].type == SHADOW_CUBE) ? CUBE_FACES : 1;
|
||||||
|
}
|
||||||
|
if ((_scene.shadowCount > 0) && (drawCount > 0) && _createShadowMaps(layers)) {
|
||||||
|
_fitShadows(drawCount);
|
||||||
|
fragmentUniforms.shadowParams[0] = SHADOW_BIAS;
|
||||||
|
fragmentUniforms.shadowParams[1] = 1.0f / (float)_scene.shadowSize;
|
||||||
|
fragmentUniforms.shadowParams[2] = (float)_scene.shadowCount;
|
||||||
|
memset(&depth, 0, sizeof(depth));
|
||||||
|
depth.clear_depth = 1.0f;
|
||||||
|
depth.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
|
depth.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
|
depth.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE;
|
||||||
|
depth.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE;
|
||||||
|
for (slot = 0; slot < _scene.shadowCount; slot++) {
|
||||||
|
ShadowT *shadow = &_scene.shadows[slot];
|
||||||
|
|
||||||
|
fragmentUniforms.shadowInfo[slot][0] = (float)shadow->type;
|
||||||
|
fragmentUniforms.shadowInfo[slot][1] = (float)shadow->layer;
|
||||||
|
fragmentUniforms.shadowInfo[slot][2] = shadow->near;
|
||||||
|
fragmentUniforms.shadowInfo[slot][3] = shadow->far;
|
||||||
|
if (shadow->type == SHADOW_CUBE) {
|
||||||
|
for (face = 0; face < CUBE_FACES; face++) {
|
||||||
|
depth.texture = _scene.shadowMaps;
|
||||||
|
depth.layer = (Uint8)(shadow->layer + face);
|
||||||
|
pass = SDL_BeginGPURenderPass(commands, NULL, 0, &depth);
|
||||||
|
_drawList(commands, pass, drawCount, &shadow->faces[face], true, NULL);
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fragmentUniforms.shadowMatrix[slot] = shadow->matrix;
|
||||||
|
depth.texture = _scene.shadowMaps;
|
||||||
|
depth.layer = (Uint8)shadow->layer;
|
||||||
|
pass = SDL_BeginGPURenderPass(commands, NULL, 0, &depth);
|
||||||
|
_drawList(commands, pass, drawCount, &shadow->matrix, true, NULL);
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Lights flagged to cast but nothing to draw into: no slots this frame.
|
||||||
|
for (x = 0; x < MAX_LIGHTS; x++) {
|
||||||
|
fragmentUniforms.lights[x].cone[2] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The main pass.
|
||||||
memset(&colour, 0, sizeof(colour));
|
memset(&colour, 0, sizeof(colour));
|
||||||
colour.clear_color = _scene.background;
|
colour.clear_color = _scene.background;
|
||||||
colour.load_op = SDL_GPU_LOADOP_CLEAR;
|
colour.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
|
|
@ -2005,71 +2477,9 @@ SDL_Texture *sceneRender(void) {
|
||||||
depth.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE;
|
depth.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE;
|
||||||
depth.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE;
|
depth.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE;
|
||||||
pass = SDL_BeginGPURenderPass(commands, &colour, 1, &depth);
|
pass = SDL_BeginGPURenderPass(commands, &colour, 1, &depth);
|
||||||
memset(&defaultMaterial, 0, sizeof(defaultMaterial));
|
_drawList(commands, pass, drawCount, &_scene.viewProjection, false, &fragmentUniforms);
|
||||||
defaultMaterial.feed = NO_HANDLE;
|
|
||||||
defaultMaterial.baseColor.x = 1.0f;
|
|
||||||
defaultMaterial.baseColor.y = 1.0f;
|
|
||||||
defaultMaterial.baseColor.z = 1.0f;
|
|
||||||
defaultMaterial.baseColor.w = 1.0f;
|
|
||||||
defaultMaterial.roughness = 0.5f;
|
|
||||||
for (x = 0; x < drawCount; x++) {
|
|
||||||
node = &_scene.nodes[_scene.draws[x].node];
|
|
||||||
mesh = &_scene.meshes[node->mesh];
|
|
||||||
material = (node->material != NO_HANDLE) ? &_scene.materials[node->material] : &defaultMaterial;
|
|
||||||
variant = _lookupPipeline(_scene.draws[x].node);
|
|
||||||
if (variant == NO_HANDLE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (variant != lastPipeline) {
|
|
||||||
SDL_BindGPUGraphicsPipeline(pass, _scene.pipelines[variant]);
|
|
||||||
lastPipeline = variant;
|
|
||||||
}
|
|
||||||
drawUniforms.modelViewProjection = mat4Multiply(_scene.viewProjection, node->world);
|
|
||||||
drawUniforms.model = node->world;
|
|
||||||
if (mat4Invert(node->world, &inverse)) {
|
|
||||||
drawUniforms.normalMatrix = mat4Transpose(inverse);
|
|
||||||
} else {
|
|
||||||
drawUniforms.normalMatrix = identity;
|
|
||||||
}
|
|
||||||
SDL_PushGPUVertexUniformData(commands, 0, &drawUniforms, sizeof(drawUniforms));
|
|
||||||
if (variant & PIPELINE_SKINNED) {
|
|
||||||
// 8 KB per skinned draw; allocated once per frame that needs it.
|
|
||||||
if (skinUniforms == NULL) {
|
|
||||||
skinUniforms = SDL_malloc(sizeof(SkinUniformsT));
|
|
||||||
if (skinUniforms == NULL) {
|
|
||||||
utilDie("Out of memory posing a skin.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_fillSkin(node, skinUniforms);
|
|
||||||
SDL_PushGPUVertexUniformData(commands, 1, skinUniforms, sizeof(SkinUniformsT));
|
|
||||||
}
|
|
||||||
fragmentUniforms.baseColor[0] = material->baseColor.x;
|
|
||||||
fragmentUniforms.baseColor[1] = material->baseColor.y;
|
|
||||||
fragmentUniforms.baseColor[2] = material->baseColor.z;
|
|
||||||
fragmentUniforms.baseColor[3] = material->baseColor.w;
|
|
||||||
fragmentUniforms.emissive[0] = material->emissive.x;
|
|
||||||
fragmentUniforms.emissive[1] = material->emissive.y;
|
|
||||||
fragmentUniforms.emissive[2] = material->emissive.z;
|
|
||||||
fragmentUniforms.emissive[3] = 1.0f;
|
|
||||||
fragmentUniforms.material[0] = material->metallic;
|
|
||||||
fragmentUniforms.material[1] = material->roughness;
|
|
||||||
fragmentUniforms.material[2] = material->unlit ? 1.0f : 0.0f;
|
|
||||||
fragmentUniforms.material[3] = (_materialTexture(material) != NULL) ? 1.0f : 0.0f;
|
|
||||||
SDL_PushGPUFragmentUniformData(commands, 0, &fragmentUniforms, sizeof(fragmentUniforms));
|
|
||||||
memset(&samplerBinding, 0, sizeof(samplerBinding));
|
|
||||||
samplerBinding.texture = (_materialTexture(material) != NULL) ? _materialTexture(material) : _scene.white;
|
|
||||||
samplerBinding.sampler = _scene.sampler;
|
|
||||||
SDL_BindGPUFragmentSamplers(pass, 0, &samplerBinding, 1);
|
|
||||||
memset(&binding, 0, sizeof(binding));
|
|
||||||
binding.buffer = mesh->vertexBuffer;
|
|
||||||
SDL_BindGPUVertexBuffers(pass, 0, &binding, 1);
|
|
||||||
binding.buffer = mesh->indexBuffer;
|
|
||||||
SDL_BindGPUIndexBuffer(pass, &binding, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
|
||||||
SDL_DrawGPUIndexedPrimitives(pass, mesh->indexCount, 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
SDL_EndGPURenderPass(pass);
|
SDL_EndGPURenderPass(pass);
|
||||||
SDL_SubmitGPUCommandBuffer(commands);
|
SDL_SubmitGPUCommandBuffer(commands);
|
||||||
SDL_free(skinUniforms);
|
|
||||||
return _scene.composite;
|
return _scene.composite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2206,6 +2616,14 @@ void sceneSetBackground(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shadow map's size in texels per side (default 1024, clamped to 256..4096); larger is
|
||||||
|
// sharper and slower. The map is rebuilt on the next frame that needs it.
|
||||||
|
void sceneSetShadowSize(int32_t size) {
|
||||||
|
_scene.shadowSize = SDL_clamp(size, SHADOW_SIZE_MIN, SHADOW_SIZE_MAX);
|
||||||
|
_destroyShadowMaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Overlay coordinates back to a world point: the point on the ray through that pixel at the given
|
// Overlay coordinates back to a world point: the point on the ray through that pixel at the given
|
||||||
// distance from the camera's near plane, using the last rendered frame's camera. Two distances
|
// distance from the camera's near plane, using the last rendered frame's camera. Two distances
|
||||||
// give a ray for picking.
|
// give a ray for picking.
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ void sceneSetAntialias(bool antialias);
|
||||||
void sceneSetAmbient(uint8_t r, uint8_t g, uint8_t b);
|
void sceneSetAmbient(uint8_t r, uint8_t g, uint8_t b);
|
||||||
void sceneComputeNormals(SceneVertexT *vertices, int32_t vertexCount, const uint32_t *indices, int32_t indexCount);
|
void sceneComputeNormals(SceneVertexT *vertices, int32_t vertexCount, const uint32_t *indices, int32_t indexCount);
|
||||||
void sceneSetBackground(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
|
void sceneSetBackground(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
|
||||||
|
void sceneSetShadowSize(int32_t size);
|
||||||
Vec3T sceneUnproject(float x, float y, float distance);
|
Vec3T sceneUnproject(float x, float y, float distance);
|
||||||
void sceneUpdateVideo(SceneVideoSourceFn source);
|
void sceneUpdateVideo(SceneVideoSourceFn source);
|
||||||
|
|
||||||
|
|
@ -76,6 +77,7 @@ bool lightSetColor(int32_t node, uint8_t r, uint8_t g, uint8_t b);
|
||||||
bool lightSetCone(int32_t node, float innerDegrees, float outerDegrees);
|
bool lightSetCone(int32_t node, float innerDegrees, float outerDegrees);
|
||||||
bool lightSetIntensity(int32_t node, float intensity);
|
bool lightSetIntensity(int32_t node, float intensity);
|
||||||
bool lightSetRange(int32_t node, float range);
|
bool lightSetRange(int32_t node, float range);
|
||||||
|
bool lightSetShadow(int32_t node, bool shadow);
|
||||||
|
|
||||||
bool materialDelete(int32_t material);
|
bool materialDelete(int32_t material);
|
||||||
int32_t materialNew(void);
|
int32_t materialNew(void);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ OUT=sceneShaders.h
|
||||||
TMP=$(mktemp -d)
|
TMP=$(mktemp -d)
|
||||||
trap 'rm -rf "$TMP"' EXIT
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
ENTRIES="vertexStatic:vertex vertexSkinned:vertex fragmentMain:fragment"
|
ENTRIES="vertexStatic:vertex vertexSkinned:vertex fragmentMain:fragment depthMain:fragment"
|
||||||
|
|
||||||
emit() {
|
emit() {
|
||||||
# emit NAME FILE: a C array from a binary file
|
# emit NAME FILE: a C array from a binary file
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
// Singe 3 scene shaders: one vertex shader for static meshes, one for skinned meshes, one
|
// Singe 3 scene shaders: one vertex shader for static meshes, one for skinned meshes, one
|
||||||
// fragment shader for both. Compiled offline by src/shaders/build.sh (SDL_shadercross) into the
|
// fragment shader for both, and an empty fragment shader for the depth-only shadow pass (which
|
||||||
|
// reuses the vertex shaders with the light's view-projection). Compiled offline by src/shaders/build.sh (SDL_shadercross) into the
|
||||||
// SPIR-V, DXIL and MSL blobs in sceneShaders.h; the engine build never compiles shaders.
|
// SPIR-V, DXIL and MSL blobs in sceneShaders.h; the engine build never compiles shaders.
|
||||||
//
|
//
|
||||||
// Resource bindings follow SDL_GPU's HLSL convention: vertex uniforms in space1, fragment
|
// Resource bindings follow SDL_GPU's HLSL convention: vertex uniforms in space1, fragment
|
||||||
// textures and samplers in space2, fragment uniforms in space3.
|
// textures and samplers in space2, fragment uniforms in space3.
|
||||||
|
|
||||||
#define MAX_LIGHTS 8
|
#define MAX_LIGHTS 8
|
||||||
#define MAX_JOINTS 128
|
#define MAX_JOINTS 128
|
||||||
|
#define MAX_SHADOWS 8
|
||||||
|
|
||||||
|
#define SHADOW_NONE 0
|
||||||
|
#define SHADOW_MAP 1
|
||||||
|
#define SHADOW_CUBE 2
|
||||||
|
|
||||||
#define LIGHT_DIRECTIONAL 0
|
#define LIGHT_DIRECTIONAL 0
|
||||||
#define LIGHT_POINT 1
|
#define LIGHT_POINT 1
|
||||||
|
|
@ -81,21 +87,112 @@ struct Light {
|
||||||
float4 positionType; // xyz position (point, spot) or unused; w = type
|
float4 positionType; // xyz position (point, spot) or unused; w = type
|
||||||
float4 directionRange; // xyz direction the light shines in (directional, spot); w = range (0 = infinite)
|
float4 directionRange; // xyz direction the light shines in (directional, spot); w = range (0 = infinite)
|
||||||
float4 color; // rgb already multiplied by intensity
|
float4 color; // rgb already multiplied by intensity
|
||||||
float4 cone; // x = cos(inner), y = cos(outer)
|
float4 cone; // x = cos(inner), y = cos(outer), z = shadow slot + 1 (0 = casts none)
|
||||||
};
|
};
|
||||||
|
|
||||||
cbuffer FragmentUniforms : register(b0, space3) {
|
cbuffer FragmentUniforms : register(b0, space3) {
|
||||||
float4 cameraPosition;
|
float4 cameraPosition;
|
||||||
float4 ambient;
|
float4 ambient;
|
||||||
float4 baseColor;
|
float4 baseColor;
|
||||||
float4 emissive;
|
float4 emissive;
|
||||||
float4 material; // x = metallic, y = roughness, z = unlit (1/0), w = textured (1/0)
|
float4 material; // x = metallic, y = roughness, z = unlit (1/0), w = textured (1/0)
|
||||||
float4 counts; // x = light count
|
float4 counts; // x = light count
|
||||||
Light lights[MAX_LIGHTS];
|
float4 shadowParams; // x = depth bias, y = 1 / map size, z = shadow count
|
||||||
|
float4x4 shadowMatrix[MAX_SHADOWS]; // Light view-projection per slot (maps)
|
||||||
|
float4 shadowInfo[MAX_SHADOWS]; // x = SHADOW_MAP / SHADOW_CUBE, y = layer, z = near, w = far (cubes)
|
||||||
|
Light lights[MAX_LIGHTS];
|
||||||
};
|
};
|
||||||
|
|
||||||
Texture2D<float4> baseTexture : register(t0, space2);
|
Texture2D<float4> baseTexture : register(t0, space2);
|
||||||
SamplerState baseSampler : register(s0, space2);
|
SamplerState baseSampler : register(s0, space2);
|
||||||
|
Texture2DArray<float> shadowMaps : register(t1, space2); // A layer per directional or spot shadow, six per point light
|
||||||
|
SamplerState shadowSampler : register(s1, space2);
|
||||||
|
|
||||||
|
|
||||||
|
// How lit a point is by a directional or spot light's shadow map: its depth from the light against
|
||||||
|
// the map, averaged over a 3x3 block of texels so edges soften. Points outside the map are lit.
|
||||||
|
float shadowFactorMap(int slot, float3 worldPosition, float3 normal, float3 toLight) {
|
||||||
|
float4 lightSpace = mul(shadowMatrix[slot], float4(worldPosition + normal * shadowParams.x * 8.0, 1.0));
|
||||||
|
float layer = shadowInfo[slot].y;
|
||||||
|
float2 uv;
|
||||||
|
float depth;
|
||||||
|
float texel = shadowParams.y;
|
||||||
|
float lit;
|
||||||
|
|
||||||
|
lightSpace.xyz /= lightSpace.w;
|
||||||
|
uv = float2(lightSpace.x * 0.5 + 0.5, 0.5 - lightSpace.y * 0.5);
|
||||||
|
depth = lightSpace.z - shadowParams.x * (1.0 + 2.0 * (1.0 - saturate(dot(normal, toLight))));
|
||||||
|
if ((uv.x < 0.0) || (uv.x > 1.0) || (uv.y < 0.0) || (uv.y > 1.0) || (depth > 1.0)) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
// Written out because DXC crashes on loops inside a function called from a loop.
|
||||||
|
lit = (shadowMaps.Sample(shadowSampler, float3(uv + float2(-texel, -texel), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(0.0, -texel), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(texel, -texel), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(-texel, 0.0), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv, layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(texel, 0.0), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(-texel, texel), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(0.0, texel), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(texel, texel), layer)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
return lit / 9.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// How lit a point is by a point light's six shadow faces. The face is picked by the direction's
|
||||||
|
// major axis and looked up with the same frame the engine rendered it with (forward, up, right =
|
||||||
|
// forward x up, a 90 degree perspective), so no cube-map convention is involved; the point's own
|
||||||
|
// projected depth is compared over five taps.
|
||||||
|
float shadowFactorCube(int slot, float3 worldPosition, float3 normal, float3 lightPosition) {
|
||||||
|
float base = shadowInfo[slot].y;
|
||||||
|
float near = shadowInfo[slot].z;
|
||||||
|
float far = shadowInfo[slot].w;
|
||||||
|
float3 dir = (worldPosition + normal * shadowParams.x * 8.0) - lightPosition;
|
||||||
|
float3 ad = abs(dir);
|
||||||
|
float texel = shadowParams.y;
|
||||||
|
float ma;
|
||||||
|
float face;
|
||||||
|
float3 forward;
|
||||||
|
float3 up;
|
||||||
|
float3 right;
|
||||||
|
float2 uv;
|
||||||
|
float depth;
|
||||||
|
float lit;
|
||||||
|
|
||||||
|
if ((ad.x >= ad.y) && (ad.x >= ad.z)) {
|
||||||
|
ma = ad.x;
|
||||||
|
face = (dir.x > 0.0) ? 0.0 : 1.0;
|
||||||
|
forward = float3((dir.x > 0.0) ? 1.0 : -1.0, 0.0, 0.0);
|
||||||
|
up = float3(0.0, 1.0, 0.0);
|
||||||
|
} else if (ad.y >= ad.z) {
|
||||||
|
ma = ad.y;
|
||||||
|
face = (dir.y > 0.0) ? 2.0 : 3.0;
|
||||||
|
forward = float3(0.0, (dir.y > 0.0) ? 1.0 : -1.0, 0.0);
|
||||||
|
up = float3(0.0, 0.0, (dir.y > 0.0) ? -1.0 : 1.0);
|
||||||
|
} else {
|
||||||
|
ma = ad.z;
|
||||||
|
face = (dir.z > 0.0) ? 4.0 : 5.0;
|
||||||
|
forward = float3(0.0, 0.0, (dir.z > 0.0) ? 1.0 : -1.0);
|
||||||
|
up = float3(0.0, 1.0, 0.0);
|
||||||
|
}
|
||||||
|
right = cross(forward, up);
|
||||||
|
uv = float2(0.5 + 0.5 * dot(dir, right) / ma, 0.5 - 0.5 * dot(dir, up) / ma);
|
||||||
|
depth = far / (far - near) - near * far / ((far - near) * ma) - shadowParams.x * 2.0;
|
||||||
|
if (depth > 1.0) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
lit = (shadowMaps.Sample(shadowSampler, float3(uv, base + face)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(texel, 0.0), base + face)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv - float2(texel, 0.0), base + face)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv + float2(0.0, texel), base + face)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
lit += (shadowMaps.Sample(shadowSampler, float3(uv - float2(0.0, texel), base + face)).r >= depth) ? 1.0 : 0.0;
|
||||||
|
return lit / 5.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shadow pass draws depth only.
|
||||||
|
void depthMain(VertexOutput input) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Named fragmentMain because "fragment" is a keyword in Metal and the MSL entry point keeps this name.
|
// Named fragmentMain because "fragment" is a keyword in Metal and the MSL entry point keeps this name.
|
||||||
|
|
@ -157,6 +254,15 @@ float4 fragmentMain(VertexOutput input) : SV_Target {
|
||||||
attenuation *= smoothstep(lights[x].cone.y, lights[x].cone.x, cosAngle);
|
attenuation *= smoothstep(lights[x].cone.y, lights[x].cone.x, cosAngle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (lights[x].cone.z > 0.5) {
|
||||||
|
int slot = (int)lights[x].cone.z - 1;
|
||||||
|
|
||||||
|
if (shadowInfo[slot].x == SHADOW_CUBE) {
|
||||||
|
attenuation *= shadowFactorCube(slot, input.worldPosition, normal, lights[x].positionType.xyz);
|
||||||
|
} else {
|
||||||
|
attenuation *= shadowFactorMap(slot, input.worldPosition, normal, toLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
diffuse = saturate(dot(normal, toLight));
|
diffuse = saturate(dot(normal, toLight));
|
||||||
halfway = normalize(toLight + view);
|
halfway = normalize(toLight + view);
|
||||||
specular = pow(saturate(dot(normal, halfway)), shininess) * (1.0 - roughness * 0.5);
|
specular = pow(saturate(dot(normal, halfway)), shininess) * (1.0 - roughness * 0.5);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
25
src/singe.c
25
src/singe.c
|
|
@ -573,6 +573,7 @@ static int32_t apiLightSetColor(lua_State *L);
|
||||||
static int32_t apiLightSetCone(lua_State *L);
|
static int32_t apiLightSetCone(lua_State *L);
|
||||||
static int32_t apiLightSetIntensity(lua_State *L);
|
static int32_t apiLightSetIntensity(lua_State *L);
|
||||||
static int32_t apiLightSetRange(lua_State *L);
|
static int32_t apiLightSetRange(lua_State *L);
|
||||||
|
static int32_t apiLightSetShadow(lua_State *L);
|
||||||
static int32_t apiMaterialDelete(lua_State *L);
|
static int32_t apiMaterialDelete(lua_State *L);
|
||||||
static int32_t apiMaterialNew(lua_State *L);
|
static int32_t apiMaterialNew(lua_State *L);
|
||||||
static int32_t apiMaterialSetBlend(lua_State *L);
|
static int32_t apiMaterialSetBlend(lua_State *L);
|
||||||
|
|
@ -640,6 +641,7 @@ static int32_t apiSceneProject(lua_State *L);
|
||||||
static int32_t apiSceneSetAmbient(lua_State *L);
|
static int32_t apiSceneSetAmbient(lua_State *L);
|
||||||
static int32_t apiSceneSetAntialias(lua_State *L);
|
static int32_t apiSceneSetAntialias(lua_State *L);
|
||||||
static int32_t apiSceneSetBackground(lua_State *L);
|
static int32_t apiSceneSetBackground(lua_State *L);
|
||||||
|
static int32_t apiSceneSetShadowSize(lua_State *L);
|
||||||
static int32_t apiSceneUnproject(lua_State *L);
|
static int32_t apiSceneUnproject(lua_State *L);
|
||||||
static int32_t apiScriptExecute(lua_State *L);
|
static int32_t apiScriptExecute(lua_State *L);
|
||||||
static int32_t apiScriptPush(lua_State *L);
|
static int32_t apiScriptPush(lua_State *L);
|
||||||
|
|
@ -3401,6 +3403,19 @@ static int32_t apiLightSetRange(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// lightSetShadow(node, bool): this light casts shadows (a point light's cost six passes)
|
||||||
|
static int32_t apiLightSetShadow(lua_State *L) {
|
||||||
|
int32_t node;
|
||||||
|
|
||||||
|
_argCheck(L, "lightSetShadow", 2, 2);
|
||||||
|
node = _argNode(L, "lightSetShadow", 1);
|
||||||
|
if (!lightSetShadow(node, _argBoolean(L, "lightSetShadow", 2))) {
|
||||||
|
_luaDie(L, "lightSetShadow", "Node %d is not a light.", node);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// materialDelete(material)
|
// materialDelete(material)
|
||||||
static int32_t apiMaterialDelete(lua_State *L) {
|
static int32_t apiMaterialDelete(lua_State *L) {
|
||||||
_argCheck(L, "materialDelete", 1, 1);
|
_argCheck(L, "materialDelete", 1, 1);
|
||||||
|
|
@ -4491,6 +4506,14 @@ static int32_t apiSceneSetBackground(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// sceneSetShadowSize(size): shadow map texels per side, 256 to 4096 (default 1024)
|
||||||
|
static int32_t apiSceneSetShadowSize(lua_State *L) {
|
||||||
|
_argCheck(L, "sceneSetShadowSize", 1, 1);
|
||||||
|
sceneSetShadowSize(_argInteger(L, "sceneSetShadowSize", 1));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// x, y, z = sceneUnproject(sx, sy, distance): the world point that far along the ray through an overlay point
|
// x, y, z = sceneUnproject(sx, sy, distance): the world point that far along the ray through an overlay point
|
||||||
static int32_t apiSceneUnproject(lua_State *L) {
|
static int32_t apiSceneUnproject(lua_State *L) {
|
||||||
_argCheck(L, "sceneUnproject", 3, 3);
|
_argCheck(L, "sceneUnproject", 3, 3);
|
||||||
|
|
@ -5973,6 +5996,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, SDL_GPUDevice *device, Co
|
||||||
lua_register(_global.luaContext, "lightSetCone", apiLightSetCone); // 3.00
|
lua_register(_global.luaContext, "lightSetCone", apiLightSetCone); // 3.00
|
||||||
lua_register(_global.luaContext, "lightSetIntensity", apiLightSetIntensity); // 3.00
|
lua_register(_global.luaContext, "lightSetIntensity", apiLightSetIntensity); // 3.00
|
||||||
lua_register(_global.luaContext, "lightSetRange", apiLightSetRange); // 3.00
|
lua_register(_global.luaContext, "lightSetRange", apiLightSetRange); // 3.00
|
||||||
|
lua_register(_global.luaContext, "lightSetShadow", apiLightSetShadow); // 3.00
|
||||||
lua_register(_global.luaContext, "materialDelete", apiMaterialDelete); // 3.00
|
lua_register(_global.luaContext, "materialDelete", apiMaterialDelete); // 3.00
|
||||||
lua_register(_global.luaContext, "materialNew", apiMaterialNew); // 3.00
|
lua_register(_global.luaContext, "materialNew", apiMaterialNew); // 3.00
|
||||||
lua_register(_global.luaContext, "materialSetBlend", apiMaterialSetBlend); // 3.00
|
lua_register(_global.luaContext, "materialSetBlend", apiMaterialSetBlend); // 3.00
|
||||||
|
|
@ -6041,6 +6065,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, SDL_GPUDevice *device, Co
|
||||||
lua_register(_global.luaContext, "sceneSetAmbient", apiSceneSetAmbient); // 3.00
|
lua_register(_global.luaContext, "sceneSetAmbient", apiSceneSetAmbient); // 3.00
|
||||||
lua_register(_global.luaContext, "sceneSetAntialias", apiSceneSetAntialias); // 3.00
|
lua_register(_global.luaContext, "sceneSetAntialias", apiSceneSetAntialias); // 3.00
|
||||||
lua_register(_global.luaContext, "sceneSetBackground", apiSceneSetBackground); // 3.00
|
lua_register(_global.luaContext, "sceneSetBackground", apiSceneSetBackground); // 3.00
|
||||||
|
lua_register(_global.luaContext, "sceneSetShadowSize", apiSceneSetShadowSize); // 3.00
|
||||||
lua_register(_global.luaContext, "sceneUnproject", apiSceneUnproject); // 3.00
|
lua_register(_global.luaContext, "sceneUnproject", apiSceneUnproject); // 3.00
|
||||||
lua_register(_global.luaContext, "scriptExecute", apiScriptExecute); // 2.00
|
lua_register(_global.luaContext, "scriptExecute", apiScriptExecute); // 2.00
|
||||||
lua_register(_global.luaContext, "scriptPush", apiScriptPush); // 2.00
|
lua_register(_global.luaContext, "scriptPush", apiScriptPush); // 2.00
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue