Apple has made it very simple to to make linear and radial gradients, but is it possible to have the color of the gradient be set by a definable function? In my situation I want to make the fill color of an object to vary with a sinus function along the x-axis. It is not hard to make pngs and use them as patterns instead, but I just wonder if it is possible to make gradients where the red, green and blue components vary along certain axis with a sinus function instead.
Any answer is appreciated. Thanks in advance.
When you create the gradient using the CAGradientLayer class, you can use the colors property with a large number of colors and vary the color components according to the sine function. There will be linear interpolation between each pair of consecutive colors which will, however, not be noticeable when the number of colors (and locations) is large enough.
Here is an example that draws a sine gradient that variates back and forth between red and blue.
// Compute the colors using a sine step-size
let samples = 100
var colors = [CGColor]()
for i in 0..<samples {
let component = CGFloat(0.5 + sin(Double(i) / Double(samples - 1) * 4.0 * M_PI) / 2.0)
colors.append(UIColor(red: component, green: 0, blue: 1 - component, alpha: 1).CGColor)
}
// Create the gradient layer
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = colors
// Install the gradient layer
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, atIndex: 0)
The end result looks like this.
Related
I made transparent object with scenekit and linked with arkit.
I made a shadow with lightning material but can't see the shadow look through the transparent object.
I made a plane and placed the object on it.
And give the light to a transparent object.
the shadow appears behind the object but can not see through the object.
Here's code that making the shadow.
let light = SCNLight()
light.type = .directional
light.castsShadow = true
light.shadowRadius = 200
light.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
light.shadowMode = .deferred
let constraint = SCNLookAtConstraint(target: model)
lightNode = SCNNode()
lightNode!.light = light
lightNode!.position = SCNVector3(model.position.x + 10, model.position.y + 30, model.position.z+30)
lightNode!.eulerAngles = SCNVector3(45.0, 0, 0)
lightNode!.constraints = [constraint]
sceneView.scene.rootNode.addChildNode(lightNode!)
And the below code is for making a floor under the bottle.
let floor = SCNFloor()
floor.reflectivity = 0
let material = SCNMaterial()
material.diffuse.contents = UIColor.white
material.colorBufferWriteMask = SCNColorMask(rawValue:0)
floor.materials = [material]
self.floorNode = SCNNode(geometry: floor)
self.floorNode!.position = SCNVector3(x, y, z)
self.sceneView.scene.rootNode.addChildNode(self.floorNode!)
I think it can be solved with simple property but I can't figure out.
How can I solve the problem?
A known issue with deferred shading is that it doesn’t work with transparency so you may have to remove that line and use the default forward shading again. That said, the “simple property” you are looking for is the .renderingOrder property on the SCNNode. Set it to 99 for example. Normally the rendering order doesn’t matter because the z buffer is used to determine what pixel is in front of others. For the shadow to show up through the transparant part of the object you need to make sure the object is rendered last.
On a different note, assuming you used some of the material settings I posted on your other question, try setting the shininess value to something like 0.4.
Note that this will still create a shadow as if the object was not transparent at all, so it won’t create a darker shadow for the label and cap. For additional realism you could opt to fake the shadow entirely, as in using a texture for the shadow and drop that on a plane which you rotate and skew as needed. For even more realism, you could fake the caustics that way too.
You may also want to add a reflection map to the reflective property of the material. Almost the same as texture map but in gray scale, where the label and cap are dark gray (not very reflective) and a lighter gray for the glass portion (else it will look like the label is on the inside of the glass). Last tip: use a Shell modifier (that’s what it’s called in 3Ds max anyway) to give the glass model some thickness.
I'm building a real-time photo editor based on CIFilters and MetalKit. But I'm running into an issue with displaying wide gamut images in a MTKView.
Standard sRGB images display just fine, but Display P3 images are washed out.
I've tried setting the CIContext.render colorspace as the image colorspace, and still experience the issue.
Here are snippets of the code:
guard let inputImage = CIImage(mtlTexture: sourceTexture!) else { return }
let outputImage = imageEditor.processImage(inputImage)
print(colorSpace)
context.render(outputImage,
to: currentDrawable.texture,
commandBuffer: commandBuffer,
bounds: inputImage.extent,
colorSpace: colorSpace)
commandBuffer?.present(currentDrawable)
let pickedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
print(pickedImage.cgImage?.colorSpace)
if let cspace = pickedImage.cgImage?.colorSpace {
colorSpace = cspace
}
I have found a similar issue on the Apple developer forums, but without any answers: https://forums.developer.apple.com/thread/66166
In order to support the wide color gamut, you need to set the colorPixelFormat of your MTKView to either BGRA10_XR or bgra10_XR_sRGB. I suspect the colorSpace property of macOS MTKViews won't be supported on iOS because color management in iOS is not active but targeted (read Best practices for color management).
Without seeing your images and their actual values, it is hard to diagnose, but I'll explain my findings & experiments. I suggest you start like I did, by debugging a single color.
For instance, what's the reddest point in P3 color space? It can be defined through a UIColor like this:
UIColor(displayP3Red: 1, green: 0, blue: 0, alpha: 1)
Add a UIButton to your view with the background set to that color for debugging purposes. You can either get the components in code to see what those values become in sRGB,
var fRed : CGFloat = 0
var fGreen : CGFloat = 0
var fBlue : CGFloat = 0
var fAlpha : CGFloat = 0
let c = UIColor(displayP3Red: 1, green: 0, blue: 0, alpha: 1)
c.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha)
or you can use the Calculator in macOS Color Sync Utility,
Make sure you select Extended Range, otherwise the values will be clamped to 0 and 1.
So, as you can see, your P3(1, 0, 0) corresponds to (1.0930, -0.2267, -0.1501) in extended sRGB.
Now, back to your MTKView,
If you set the colorPixelFormat of your MTKView to .BGRA10_XR, then you obtain the brightest red if the output of your shader is,
(1.0930, -0.2267, -0.1501)
If you set the colorPixelFormat of your MTKView to .bgra10_XR_sRGB, then you obtain the brightest red if the output of your shader is,
(1.22486, -0.0420312, -0.0196301)
because you have to write a linear RGB value, since this texture format will apply the gamma correction for you. Be careful when applying the inverse gamma, since there are negative values. I use this function,
let f = {(c: Float) -> Float in
if fabs(c) <= 0.04045 {
return c / 12.92
}
return sign(c) * powf((fabs(c) + 0.055) / 1.055, 2.4)
}
The last missing piece is creating a wide gamut UIImage. Set the color space to CGColorSpace.displayP3 and copy the data over. But what data, right? The brightest red in this image will be
(1, 0, 0)
or (65535, 0, 0) in 16-bit ints.
What I do in my code is using .rgba16Unorm textures to manipulate images in displayP3 color space, where (1, 0, 0) will be the brightest red in P3. This way, I can directly copy over its contents to a UIImage. Then, for displaying, I pass a color transform to the shader to convert from P3 to extended sRGB (so, not saturating colors) before displaying. I use linear color, so my transform is just a 3x3 matrix. I set my view to .bgra10_XR_sRGB, so the gamma will be applied automatically for me.
That (column-major) matrix is,
1.2249 -0.2247 0
-0.0420 1.0419 0
-0.0197 -0.0786 1.0979
You can read about how I generated it here: Exploring the display-P3 color space
Here's an example I built using UIButtons and an MTKView, screen-captured on an iPhoneX,
The button on the left is the brightest red on sRGB, while the button on the right is using a displayP3 color. At the center, I placed an MTKView that outputs the transformed linear color as described above.
Same experiment for green,
Now, if you see this on a recent iPhone or iPad, you should see the both the square in the center and the button to the right have the same bright colors. If you see this on a Mac that can't display them, the left button will appear the same color. If you see this in a Windows machine or a browser without proper color management, the left button may also appear to be of a different color, but that's only because the whole image is interpreted as sRGB and obviously those pixels have different values... But the appearance won't be correct.
If you want more references, check the testP3UIColor unit test I added here: ColorTests.swift,
my functions to initialize the UIImage: Image.swift,
and a sample app to try out the conversions: SampleColorPalette
I haven't experimented with CIImages, but I guess the same principles apply.
I hope this information is of some help. It also took me long to figure out how to display colors properly because I couldn't find any explicit reference to displayP3 support in the Metal SDK documentation.
I know there are several other posts about this, but my case is kinda specific, i haven't seen this one yet.
I have in my game a ball-shaped sprite, that whenever I tap on it, I would like to add a colorised version of the very same sprite but with an effect of fadeIn and fadeOut.
Going to give you an example code:
self.ball = SKSpriteNode(imageNamed: "ball")
self.ball.position = CGPoint(x: midX, y: midY)
self.ball.zPosition = 1
self.ball.size = CGSize(width: 100, height: 100)
self.touchEffect = SKSpriteNode(imageNamed: "ball")
self.touchEffect.position = CGPoint(x: 0, y: 0)
self.touchEffect.zPosition = 2
self.touchEffect.size = CGSize(width: 100, height: 100)
self.touchEffect.color = UIColor.whiteColor()
self.touchEffect.blendColorFactor = 1
self.touchEffect.alpha = 0
self.ball.addChild(self.touchEffect)
self.addChild(self.ball)
Now, up to this point... I can't even see the touchEffect sprite colorised (if I put alpha to 1), but the same color of the original sprite. Why is this?
At the touchesBegan I do something like this:
func showTapEffect() {
let fadeIn = SKAction.fadeInWithDuration(0.3)
let fadeOut = SKAction.fadeOutWithDuration(0.3)
let sequence = SKAction.sequence([fadeIn,fadeOut])
self.touchEffect.runAction(sequence)
}
I also used it in different scenarios within my very same game, and it worked. Don't know why this unique case doesn't. Any hint? If you need more example code, let me know, is not a copy paste of my current code tho, I just typed it from my memory so you might see a typo error in there. But the code is very alike.
(And my sprite isn't dark or black)
Thanks in advance.
I am posting this an an answer because it is too large for comments, and I can post code in here if need be.
I just reread your thing like 3 times over, You are blending with White, what color are you expecting to get? if you blend Blue and White, you get Blue, if you blend Purple and Blue, You would get Blue. If you blend Blue and Gray, you get a darker Blue. It is all percentage multiplication. I do not believe you get a blend mode to pick from with colors. How it works is it takes the color of each pixel, and on the pixel, breaks it up into R,G,B, then it takes your color, and breaks that up into R G B (lets call it CR CG CB). The math becomes (R * CR,G * CG,B * CB) on a per pixel level.
You are doing (R * 1,G * 1,B * 1) which is (R,G,B)
If you want to colorize your sprite, then you need your sprite to be a gray scale image, and use colors only when you want them to stay that color (To a degree, because blending will still apply to them, you need to figure out the math on how you want it to blend)
I'm looking to add multiple drop shadows with different opacities to a view. The specs for the shadows are as follows:
Y-offset of 4 with blur radius of 1
Y-offset of 10 with blur radius of 10
Y-offset of 2 with blur radius of 4
Blur radius of 1, spread of 1 (no offsets, will probably have to be 4 different shadows)
I can get all this working just fine using CALayers. Here's the code I have working for that (please note that I haven't bothered to set shadowPath yet, and won't until I get the multiple shadows thing working):
layer.cornerRadius = 4
layer.masksToBounds = false
layer.shouldRasterize = true
let layer2 = CALayer(layer: layer), layer3 = CALayer(layer: layer), layer4 = CALayer(layer: layer)
layer.shadowOffset = CGSizeMake(0, 4)
layer.shadowRadius = 1
layer2.shadowOffset = CGSizeMake(0, 10)
layer2.shadowRadius = 10
layer2.shadowColor = UIColor.blackColor().CGColor
layer2.shouldRasterize = true //Evidently not copied during initialization from self.layer
layer3.shadowOffset = CGSizeMake(0, 2)
layer3.shadowRadius = 4
layer3.shouldRasterize = true
layer4.shadowOffset = CGSizeMake(0, 1)
layer4.shadowRadius = 1
layer4.shadowOpacity = 0.1
layer4.shouldRasterize = true
layer.addSublayer(layer2)
layer.addSublayer(layer3)
layer.addSublayer(layer4)
(While this code is in Swift, I trust that it looks familiar enough to most Cocoa/Objective-C developers for it to make sense. Just know that layer is equivalent to self.layer in this context.)
The problem, however, arises when I attempt to use different opacities for each shadow. The shadowOpacity property of layer ends up being applied to all of its sublayers. This is a problem, as I need all of them to have their own shadow opacity. I have tried setting each layer's shadow opacity to its correct value (0.04, 0.12, etc.), but then the opacity of 0.04 of layer is applied to all sublayers. So I tried to set layer.shadowOpacity to 1.0, but this made all the shadows solid black. I also tried to be clever and do layer2.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.12).CGColor, but it was just changed to full black with no transparency.
I suppose it makes some sort of sense that the layers should all have the same shadow opacity. But what's a way to get this working, varying opacities and all (doesn't have to utilize CALayer if it's easier another way)?
Please don't answer with "just use an image": no matter how sane that may be, I'm trying to avoid it. Just humor me.
Thanks.
EDIT: As per request, here's what I'm after: .
The key thing that needs to be added is setting the layers' shadowPath. By default, Core Graphics draws a shadow around the layer's visible content, but in your code neither backgroundColor nor bounds are set for the layers, so the layers are actually empty.
Assuming you have a UIView subclass, you can make it work by adding something like this:
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.forEach { (sublayer) in
sublayer.shadowPath = UIBezierPath(rect: bounds).cgPath
}
}
I tested this approach on a view with multiple shadows and it worked as expected, as soon as the shadowPath is defined for the shadow layers. Different shadow colors and opacities worked as well, but you have to keep in mind that upper layers in the hierarchy will overlap the layers behind them, so if the front layer has a thick shadow, the other shadows can get hidden by it.
What about adding the alpha to the shadow color instead of the layer shadow opacity?
i.e. instead of
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.5
do
layer.shadowColor = UIColor.black.withAlphaComponent(0.5).cgColor
for each layer.
How can I use a CAGradientLayer to most efficiently draw a gradient around a circle / with angles?
I made the one underneath with the help of this project. It uses a bitmap context for drawing but a CAGradientLayer would be far more efficient.
Unfortunately I could only figure out how to make linear gradients with it.
I realize many years have passed since this question was asked, but for anybody who stumbles across it now, iOS 12 added a conic gradient type that makes this possible.
gradientLayer.type = CAGradientLayerType.conic
gradientLayer.frame = bounds
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
Gradient layers currently only support linear gradients. However, if you look at the interface for gradient layers, it includes a type property. Right now the only type defined is kCAGradientLayerAxial (linear).
The fact that there is a type property suggests that Apple will be adding more types at some future date, and radial gradients seem like a very like addition.
You might look at creating your own custom subclass of CAGradientLayer that draws radial gradients as well as linear. I've seen demo projects on the net that create custom CALayer subclasses.