Deferred Screenspace Decals in Metal - ios

Trying to create deferred screenspace decals rendering in Metal by following this article. Though can't seem to figure it out...
These are bounds of the decal...
Actual result...
Potential issue
So apparently it doesn't think that the decal is intersecting the mesh, I'm sampling the depth value correctly, but then when calculating the actual position the the pixel in 3D space something doesn't add up.
Code
vertex VertexOut vertex_decal(
const VertexIn in [[ stage_in ]],
constant DecalVertexUniforms &uniforms [[ buffer(2) ]]
) {
VertexOut out;
out.position = uniforms.projectionMatrix * uniforms.viewMatrix * uniforms.modelMatrix * in.position;
out.viewPosition = (uniforms.viewMatrix * uniforms.modelMatrix * in.position).xyz;
out.normal = uniforms.normalMatrix * in.normal;
out.uv = in.uv;
return out;
}
fragment float4 fragment_decal(
const VertexOut in [[ stage_in ]],
constant DecalFragmentUniforms &uniforms [[ buffer(3) ]],
depth2d<float, access::sample> depthTexture [[ texture(0) ]]
) {
constexpr sampler textureSampler (mag_filter::nearest, min_filter::nearest);
float2 resolution = float2(
depthTexture.get_width(),
depthTexture.get_height()
);
float2 textureCoordinate = in.position.xy / resolution;
float depth = depthTexture.sample(textureSampler, textureCoordinate);
float3 viewRay = in.viewPosition * (uniforms.farClipPlane / in.viewPosition.z);
float3 viewPosition = viewRay * depth;
float3 worldPositon = (uniforms.inverseViewMatrix * float4(viewPosition, 1)).xyz;
float3 objectPositon = (uniforms.inverseModelMatrix * float4(worldPositon, 1)).xyz;
float distX = 0.5 - abs(objectPositon.x);
float distY = 0.5 - abs(objectPositon.y);
float distZ = 0.5 - abs(objectPositon.z);
if(distX > 0 && distY > 0 && distZ > 0) {
return float4(1, 0, 0, 0.5);
} else {
discard_fragment();
}
}
EDIT:
Made a bit of a progress, now it at least renders something, it clips the decal box correctly once its outside of some mesh, but the parts on the mesh are still not completely correct.. to be exact it also renders sides of the box that are overlapping with the mesh under the decal (you can see it on the image below as the red there is a bit darker)
And to add more details, the depthTexture is passed from previous "pass" so it only contains the icosphere on it, and the decal cube shader doesn't write to the depthTexture, just reads from it.
and depth stencil is defined as...
let stencilDescriptor = MTLDepthStencilDescriptor()
stencilDescriptor.depthCompareFunction = .less
stencilDescriptor.isDepthWriteEnabled = false
and render pipeline is defined as...
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexDescriptor = vertexDescriptor
renderPipelineDescriptor.vertexFunction = vertexLibrary.makeFunction(name: "vertex_decal")
renderPipelineDescriptor.fragmentFunction = fragmentLibrary.makeFunction(name: "fragment_decal")
if let colorAttachment = renderPipelineDescriptor.colorAttachments[0] {
colorAttachment.pixelFormat = .bgra8Unorm
colorAttachment.isBlendingEnabled = true
colorAttachment.rgbBlendOperation = .add
colorAttachment.sourceRGBBlendFactor = .sourceAlpha
colorAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha
}
renderPipelineDescriptor.colorAttachments[1].pixelFormat = .bgra8Unorm
renderPipelineDescriptor.depthAttachmentPixelFormat = .depth32Float
so the current issue is that it discards only pixels that are out of the mesh that its being projected on, instead of all pixels that are "above" the surface of the icosphere
New Shader Code
fragment float4 fragment_decal(
const VertexOut in [[ stage_in ]],
constant DecalFragmentUniforms &uniforms [[ buffer(3) ]],
depth2d<float, access::sample> depthTexture [[ texture(0) ]]
) {
constexpr sampler textureSampler (mag_filter::nearest, min_filter::nearest);
float2 resolution = float2(
depthTexture.get_width(),
depthTexture.get_height()
);
float2 textureCoordinate = in.position.xy / resolution;
float depth = depthTexture.sample(textureSampler, textureCoordinate);
float3 screenPosition = float3(textureCoordinate * 2 - 1, depth);
float4 viewPosition = uniforms.inverseProjectionMatrix * float4(screenPosition, 1);
float4 worldPosition = uniforms.inverseViewMatrix * viewPosition;
float3 objectPosition = (uniforms.inverseModelMatrix * worldPosition).xyz;
if(abs(worldPosition.x) > 0.5 || abs(worldPosition.y) > 0.5 || abs(worldPosition.z) > 0.5) {
discard_fragment();
} else {
return float4(1, 0, 0, 0.5);
}
}

Finally managed to get it to work properly, so the final shader code is...
the issues that the latest shader had were...
Flipped Y axis on screenPosition
Not converting the objectPosition to NDC space (localPosition)
fragment float4 fragment_decal(
const VertexOut in [[ stage_in ]],
constant DecalFragmentUniforms &uniforms [[ buffer(3) ]],
depth2d<float, access::sample> depthTexture [[ texture(0) ]],
texture2d<float, access::sample> colorTexture [[ texture(1) ]]
) {
constexpr sampler depthSampler (mag_filter::linear, min_filter::linear);
float2 resolution = float2(
depthTexture.get_width(),
depthTexture.get_height()
);
float2 depthCoordinate = in.position.xy / resolution;
float depth = depthTexture.sample(depthSampler, depthCoordinate);
float3 screenPosition = float3((depthCoordinate.x * 2 - 1), -(depthCoordinate.y * 2 - 1), depth);
float4 viewPosition = uniforms.inverseProjectionMatrix * float4(screenPosition, 1);
float4 worldPosition = uniforms.inverseViewMatrix * viewPosition;
float4 objectPosition = uniforms.inverseModelMatrix * worldPosition;
float3 localPosition = objectPosition.xyz / objectPosition.w;
if(abs(localPosition.x) > 0.5 || abs(localPosition.y) > 0.5 || abs(localPosition.z) > 0.5) {
discard_fragment();
} else {
float2 textureCoordinate = localPosition.xy + 0.5;
float4 color = colorTexture.sample(depthSampler, textureCoordinate);
return float4(color.rgb, 1);
}
}
The final results look like this (red are pixels that are kept, blue pixels are discarded)...

Related

Adding Light Falloff for multiple Point Lights

I'm currently trying to add multiple point lights to my game. What I have done appears to be mostly working, except for a small problem of blending light falloff. Here's two images to show you what's happening. In the first one, Light Falloff is commented out. Both point lights appear correctly.
And here's the second image, where I have light falloff enabled. You will see that only light #2 is "mostly" visible. There are traces of light #1, but for the most part, light #1 appears to be overridden by light #2's falloff. In other words, each consecutive light's falloff overrides the light from previous lights.
Does anyone know how to add falloff for multiple point lights? I'm sure I'm doing something slightly wrong, and that's why the lights are not properly accumulated.
Here's my shader:
struct Vertex
{
float4 pos : POSITION;
float2 tex : TEXTURE;
float3 norm : NORMAL;
};
struct PixelShaderArgs
{
float4 pos : SV_POSITION;
float2 col : TEXTURE;
float3 norm : NORMAL;
float3 worldPos : POSITION;
};
struct PointLightShaderArgs
{
float3 pos;
float radius;
float intensity;
float3 padding;
float4 ambient;
float4 diffuse;
};
Texture2D ShaderTexture : register(t0);
SamplerState Sampler : register(s0);
float4x4 localMatrix : register(b0);
cbuffer ShaderDataBuffer : register(b1)
{
float2 TextureResolution;
};
cbuffer cbPerFrame : register(b3)
{
PointLightShaderArgs light[8];
};
cbuffer WorldPositionBuffer : register(b4)
{
float4x4 World;
};
PixelShaderArgs VertexShaderMain(Vertex vertex)
{
PixelShaderArgs output;
output.pos = mul(vertex.pos, localMatrix);
output.col = vertex.tex;
output.norm = mul(vertex.norm, World);
output.worldPos = mul(vertex.pos, World);
return output;
}
int2 convertUVToPixel(float u, float v)
{
int width = TextureResolution.x;
int height = TextureResolution.y;
int xCoordinate = floor(u * width);
int yCoordinate = floor(v * height);
return int2(xCoordinate % width, yCoordinate % height);
}
float Falloff(float distance, float radius)
{
return clamp(1.0f - (distance / radius), 0.0, 1.0);
}
#define ATTENUATION_CONSTANT 1.0f // 0% Constant
#define ATTENUATION_LINEAR 0.0f // 100% Linear
#define ATTENUATION_QUADRATIC 0.0f // 100% Quadratic
float4 PixelShaderMain(PixelShaderArgs pixelShaderArgs) : SV_Target
{
float u = pixelShaderArgs.col.x;
float v = pixelShaderArgs.col.y;
// Lighting
float3 fragColor = float3(0.0f, 0.0f, 0.0f);
float4 diffuse = ShaderTexture.Load(int3(convertUVToPixel(u, v), 0));
for (int i = 0; i < 2; i++)
{
float3 ambient = diffuse * light[i].ambient;
pixelShaderArgs.norm = normalize(pixelShaderArgs.norm);
float3 lightToPixelVec = light[i].pos - pixelShaderArgs.worldPos;
float distance = length(lightToPixelVec);
float luminosity = dot(lightToPixelVec / distance, pixelShaderArgs.norm);
float intensity = 1.00f;
if (luminosity > 0.0f)
{
// Do lighting attenuation
fragColor += luminosity * diffuse * light[i].diffuse;
fragColor /= ATTENUATION_CONSTANT + (ATTENUATION_LINEAR * distance) + (ATTENUATION_QUADRATIC * (distance * distance));
fragColor *= light[i].intensity; // multiply the final result by the intensity.
fragColor *= Falloff(distance, light[i].radius); // This is what's causing the problem!!
//fragColor = saturate(fragColor + ambient);
}
}
return float4(fragColor, diffuse.a);
}
I figured this out. The solution was to move the falloff calculation up and inline it with the following line: fragColor += luminosity * diffuse * light[i].diffuse * Falloff(distance,light[i].radius);
This results the correcting falloff blending, shown in this picture:
and another picture showing three overlapped point lights:
Here's the updated shader (A lot of changes were made from the first one because I'm actually posting this answer late)
struct Vertex
{
float4 pos : POSITION;
float2 tex : TEXTURE;
float3 norm : NORMAL;
};
struct PixelShaderArgs
{
float4 pos : SV_POSITION;
float2 col : TEXTURE;
float3 norm : NORMAL;
float3 worldPos : POSITION;
};
struct PointLightShaderArgs
{
float3 pos;
float radius;
float intensity;
float3 padding;
float4 ambient;
float4 diffuse;
};
Texture2D ShaderTexture : register(t0);
SamplerState Sampler : register(s0);
float4x4 localMatrix : register(b0);
cbuffer ShaderDataBuffer : register(b1)
{
float2 TextureResolution;
};
cbuffer cbPerFrame : register(b3)
{
PointLightShaderArgs light[32];
};
cbuffer WorldPositionBuffer : register(b4)
{
float4x4 World;
};
PixelShaderArgs VertexShaderMain(Vertex vertex)
{
PixelShaderArgs output;
output.pos = mul(vertex.pos, localMatrix);
output.col = vertex.tex;
output.norm = mul(vertex.norm, World);
output.worldPos = mul(vertex.pos, World);
return output;
}
int2 convertUVToPixel(float u, float v)
{
int width = TextureResolution.x;
int height = TextureResolution.y;
int xCoordinate = floor(u * width);
int yCoordinate = floor(v * height);
return int2(xCoordinate % width, yCoordinate % height);
}
float Falloff(float distance, float radius)
{
return clamp(1.0f - (distance / radius), 0.0, 1.0);
}
#define ATTENUATION_CONSTANT 1.0f // 0% Constant
#define ATTENUATION_LINEAR 0.0f // 100% Linear
#define ATTENUATION_QUADRATIC 0.0f // 100% Quadratic; Democrats are domestic terrorists
float4 PixelShaderMain(PixelShaderArgs pixelShaderArgs) : SV_Target
{
float u = pixelShaderArgs.col.x;
float v = pixelShaderArgs.col.y;
// Lighting
float3 fragColor = float3(0.0f, 0.0f, 0.0f);
float4 diffuse = ShaderTexture.Load(int3(convertUVToPixel(u, v), 0));
for (int i = 0; i < 32; i++)
{
float3 ambient = diffuse * light[i].ambient;
pixelShaderArgs.norm = normalize(pixelShaderArgs.norm);
float3 lightToPixelVec = light[i].pos - pixelShaderArgs.worldPos;
float distance = length(lightToPixelVec);
float luminosity = dot(lightToPixelVec / distance, pixelShaderArgs.norm);
float intensity = 1.00f;
if (luminosity > 0.0f)
{
// Do lighting attenuation
fragColor += luminosity * diffuse * light[i].diffuse * Falloff(distance,light[i].radius);
fragColor /= ATTENUATION_CONSTANT + (ATTENUATION_LINEAR * distance) + (ATTENUATION_QUADRATIC * (distance * distance));
fragColor *= light[i].intensity; // multiply the final result by the intensity.
}
fragColor = saturate(fragColor + ambient);
}
return float4(fragColor, diffuse.a);
}

convert the fragment Shader written for GLSL to Metal Shader

I would like to convert the following fragment Shader written for glsl to Metal Shader.
const float PI = 3.14159265359;
mat2 rotate2d (float _angle) {
return mat2 (cos (_angle), -sin (_angle),
sin (_angle), cos (_angle));
}
void main (void) {
vec2 st = (gl_FragCoord.xy * 2.0 --resolution) /min(resolution.x,resolution.y);
float p = 0.0;
st = rotate2d (sin (time) * PI) * st;
vec2 c = max (abs (st) --0.2,0.0);
p = length (c);
p = ceil (p);
vec3 color = vec3 (1.0-p);
gl_FragColor = vec4 (color, 1.0);
}
At that time, I understand that there is no problem if vec2 etc. is set to float2 etc.
How should I write it?
It's hard to convert this shader without having any informartion about your current render pipeline:
#include <metal_stdlib>
float2x2 rotate2d(float angle)
{
return float2x2(float2(cos(angle), -sin(angle)),
float2(sin(angle), cos(angle)));
}
fragment float4 fragmentShader(RasterizerData in [[stage_in]],
constant simd::float2 &resolution [[ buffer(0) ]],
constant float &time [[ buffer(1) ]])
{
float2 st = (in.textureCoordinate.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
float p = 0.0;
st = rotate2d(sin(time) * M_PI_F) *st;
float2 c = max(abs(st) - 0.2, 0.0);
p = length(c);
p = ceil(p);
float3 color = float3(1.0 - p);
return float4(color, 1);
}

METAL - Fragment shader is producing noise

I am trying to use basic lighting techniques described in Metal By Tutorials to produce a diffuse, ambient, and specular light on my models.
I have used this lighting algorithm plenty of times and it works great, but the past 2 times the specular color seems to produce a characteristic green and yellow noise. Even without the specular color, the diffuse and ambient colors seem to produce this awful noise.
Any ideas as to why this would be happening?
I wonder if the fact that I do not use a renderer class is causing this problem.
Here is fragment shader code:
fragment float4 fragmentmain(const VOUT in [[stage_in]],
texture2d<float> texture1 [[texture(0)]],
constant FRAGMENTUNIFORMS &data [[buffer(2)]],
constant LIGHT *lights [[buffer(3)]])
{
constexpr sampler texturesampler;
float3 basecolor = texture1.sample(texturesampler, in.coords).rgb;
float3 diffusecolor;
float3 ambientcolor;
float3 specularcolor;
float3 materialspecularcolor = float3(1,1,1);
float shine = 32;
float3 normaldirection = normalize(in.normal);
for (uint i = 0 ; i < data.lightcount ; i++) {
LIGHT light = lights[i];
if (light.type == sun) {
float3 lightdirection = normalize(-light.position);
float diffuseintensity = saturate(-dot(lightdirection, normaldirection));
diffusecolor = light.color * basecolor * diffuseintensity;
if (diffuseintensity > 0) {
float3 reflection = reflect(lightdirection, normaldirection);
float3 cameradirection = normalize(in.position.xyz - data.cameraposition);
float specularintensity = pow(saturate(-dot(reflection, cameradirection)), shine);
specularcolor = light.color * materialspecularcolor * specularintensity;
}
} else if (light.type == ambient) {
ambientcolor = light.color * light.intensity;
}
}
float3 color = diffusecolor + ambientcolor + specularcolor;
return float4(color, 1);
}
Interesting solution to the problem.
I changed:
float3 diffusecolor;
float3 ambientcolor;
float3 specularcolor;
to
float3 diffusecolor = float3(0,0,0);
float3 ambientcolor = float3(0,0,0);
float3 specularcolor = float3(0,0,0);
and the image turned entirely black. No more noise. However, it seemed that my lights were not being iterated through in the for loop.
It turned out that my light.type enum was set to
enum LIGHTTYPE {
sun = 1,
ambient = 2
};
but when I changed it to:
enum LIGHTTYPE {
sun = 0,
ambient = 1
};
the issue was totally resolved. Sorry!

Metal shader 3D texture lookup

I created a 3D texture from a LUT file on iOS as follows:
let dim = 33
let textureDescriptor = MTLTextureDescriptor()
textureDescriptor.textureType = .type3D
textureDescriptor.pixelFormat = .rgba32Float
textureDescriptor.width = dim
textureDescriptor.height = dim
textureDescriptor.depth = dim
textureDescriptor.usage = .shaderRead
let texture = device.makeTexture(descriptor: textureDescriptor)
texture!.replace(region: MTLRegionMake3D(0, 0, 0, dim, dim, dim),
mipmapLevel:0,
slice:0,
withBytes:values!,
bytesPerRow:dim * MemoryLayout<Float>.size * 4,
bytesPerImage:dim * dim * MemoryLayout<Float>.size * 4)
and then I try to use this LUT in fragment shader as follows:
fragment half4 fragmentShader ( MappedVertex in [[ stage_in ]],
texture2d<float, access::sample> inputTexture [[ texture(0) ]],
texture3d<float, access::sample> lutTexture [[ texture(1) ]]
)
{
constexpr sampler s(s_address::clamp_to_edge, t_address::clamp_to_edge, min_filter::linear, mag_filter::linear);
float3 rgb = inputTexture.sample(s, in.textureCoordinate).rgb;
float3 lookupColor = lutTexture.sample(s, rgb).rgb;
return half4(half3(lookupColor), 1.h);
}
I am afraid that I'm not getting the correct results. Is everything in the code perfect? Am I sampling the 3d texture correctly?

Execution of the command buffer was aborted due to an error during execution

I've been stuck on this for a while; this isn't a particularly expensive shader (at least, based on my very limited experience with Metal), yet I still get this message for the first few frames:
Execution of the command buffer was aborted due to an error during execution. Caused GPU Hang Error (IOAF code 3)
And then I get this one all subsequent frames:
Execution of the command buffer was aborted due to an error during execution. Ignored (for causing prior/excessive GPU errors) (IOAF code 4)
This is the vertex shader, which doesn't really do anything:
vertex VertexOut viewportProgram(uint vertexID [[ vertex_id ]],
constant float2 *positions [[ buffer(0) ]],
constant float2 *texcoords [[ buffer(1) ]]) {
VertexOut out;
out.position.xy = positions[vertexID];
out.position.z = 0.0; // Only 2D; no depth
out.position.w = 1.0; // Only 2D; no perspective divide
out.texcoord = texcoords[vertexID];
return out;
}
Here's my fragment shader, which converts colors from YUV to RGB:
fragment float4 colorConvertProgram(VertexOut in [[stage_in]],
texture2d<float, access::sample> yTexture [[ texture(0) ]],
texture2d<float, access::sample> uvTexture [[ texture(1) ]],
sampler textureSampler [[ sampler(0) ]]) {
float3 colorOffset = float3(-(16.0/255.0), -0.5, -0.5);
float3x3 colorMatrix = float3x3(float3(1.164, 0.000, 1.596),
float3(1.164, -0.392, -0.813),
float3(1.164, 2.017, 0.000));
float3 yuv = float3(yTexture.sample(textureSampler, in.texcoord).r,
uvTexture.sample(textureSampler, in.texcoord).rg);
float3 rgb = (yuv + colorOffset) * colorMatrix;
return float4(rgb, 1.0);
}
And this is my Swift code that puts it all together:
let samplerDescriptor = MTLSamplerDescriptor()
samplerDescriptor.minFilter = .linear
samplerDescriptor.mipFilter = .linear
samplerDescriptor.sAddressMode = .clampToZero
samplerDescriptor.tAddressMode = .clampToZero
let sampler = device.makeSamplerState(descriptor: samplerDescriptor)
renderEncoder.setViewport(MTLViewport(originX: 0.0, originY: 0.0,
width: Double(bounds.width),
height: Double(bounds.height),
znear: -1.0, zfar: 1.0))
renderEncoder.setRenderPipelineState(renderPipelineState)
renderEncoder.setVertexBuffer(vertexPositionBuffer, offset: 0, index: 0)
renderEncoder.setVertexBuffer(vertexTexcoordBuffer, offset: 0, index: 1)
renderEncoder.setFragmentTexture(yTexture, index: 0)
renderEncoder.setFragmentTexture(uvTexture, index: 1)
renderEncoder.setFragmentSamplerState(sampler, index: 0)
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
renderEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()

Resources